Skip to content

Commit 41e6094

Browse files
committed
add warning message when a remote configuration include an another remote config
Signed-off-by: Guillaume Lours <[email protected]>
1 parent 66a4716 commit 41e6094

File tree

3 files changed

+157
-1
lines changed

3 files changed

+157
-1
lines changed

cmd/compose/options.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/compose-spec/compose-go/v2/template"
3030
"github.com/compose-spec/compose-go/v2/types"
3131
"github.com/docker/cli/cli/command"
32+
"github.com/docker/compose/v2/internal/tracing"
3233
ui "github.com/docker/compose/v2/pkg/progress"
3334
"github.com/docker/compose/v2/pkg/prompt"
3435
"github.com/docker/compose/v2/pkg/utils"
@@ -103,6 +104,11 @@ func checksForRemoteStack(ctx context.Context, dockerCli command.Cli, project *t
103104
if !isRemoteConfig(dockerCli, options) {
104105
return nil
105106
}
107+
if metrics, ok := ctx.Value(tracing.MetricsKey{}).(tracing.Metrics); ok && metrics.CountIncludesRemote > 0 {
108+
if err := confirmRemoteIncludes(dockerCli, options, assumeYes); err != nil {
109+
return err
110+
}
111+
}
106112
displayLocationRemoteStack(dockerCli, project, options)
107113
return promptForInterpolatedVariables(ctx, dockerCli, options.ProjectOptions, assumeYes, cmdEnvs)
108114
}
@@ -245,3 +251,41 @@ func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, o
245251
_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir)
246252
}
247253
}
254+
255+
func confirmRemoteIncludes(dockerCli command.Cli, options buildOptions, assumeYes bool) error {
256+
if assumeYes {
257+
return nil
258+
}
259+
260+
var remoteIncludes []string
261+
remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli)
262+
for _, cf := range options.ProjectOptions.ConfigPaths {
263+
for _, loader := range remoteLoaders {
264+
if loader.Accept(cf) {
265+
remoteIncludes = append(remoteIncludes, cf)
266+
break
267+
}
268+
}
269+
}
270+
271+
if len(remoteIncludes) == 0 {
272+
return nil
273+
}
274+
275+
_, _ = fmt.Fprintln(dockerCli.Out(), "\nWarning: This Compose project includes files from remote sources:")
276+
for _, include := range remoteIncludes {
277+
_, _ = fmt.Fprintf(dockerCli.Out(), " - %s\n", include)
278+
}
279+
_, _ = fmt.Fprintln(dockerCli.Out(), "\nRemote includes could potentially be malicious. Make sure you trust the source.")
280+
281+
msg := "Do you want to continue? [y/N]: "
282+
confirmed, err := prompt.NewPrompt(dockerCli.In(), dockerCli.Out()).Confirm(msg, false)
283+
if err != nil {
284+
return err
285+
}
286+
if !confirmed {
287+
return fmt.Errorf("operation cancelled by user")
288+
}
289+
290+
return nil
291+
}

cmd/compose/options_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bytes"
2121
"context"
2222
"fmt"
23+
"io"
2324
"os"
2425
"path/filepath"
2526
"strings"
@@ -280,3 +281,114 @@ services:
280281
normalizeSpaces(actualOutput),
281282
"\nExpected:\n%s\nGot:\n%s", expected, actualOutput)
282283
}
284+
285+
func TestConfirmRemoteIncludes(t *testing.T) {
286+
ctrl := gomock.NewController(t)
287+
defer ctrl.Finish()
288+
cli := mocks.NewMockCli(ctrl)
289+
290+
tests := []struct {
291+
name string
292+
opts buildOptions
293+
assumeYes bool
294+
userInput string
295+
wantErr bool
296+
errMessage string
297+
wantPrompt bool
298+
wantOutput string
299+
}{
300+
{
301+
name: "no remote includes",
302+
opts: buildOptions{
303+
ProjectOptions: &ProjectOptions{
304+
ConfigPaths: []string{
305+
"docker-compose.yaml",
306+
"./local/path/compose.yaml",
307+
},
308+
},
309+
},
310+
assumeYes: false,
311+
wantErr: false,
312+
wantPrompt: false,
313+
},
314+
{
315+
name: "assume yes with remote includes",
316+
opts: buildOptions{
317+
ProjectOptions: &ProjectOptions{
318+
ConfigPaths: []string{
319+
"oci://registry.example.com/stack:latest",
320+
"git://github.com/user/repo.git",
321+
},
322+
},
323+
},
324+
assumeYes: true,
325+
wantErr: false,
326+
wantPrompt: false,
327+
},
328+
{
329+
name: "user confirms remote includes",
330+
opts: buildOptions{
331+
ProjectOptions: &ProjectOptions{
332+
ConfigPaths: []string{
333+
"oci://registry.example.com/stack:latest",
334+
"git://github.com/user/repo.git",
335+
},
336+
},
337+
},
338+
assumeYes: false,
339+
userInput: "y\n",
340+
wantErr: false,
341+
wantPrompt: true,
342+
wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" +
343+
" - oci://registry.example.com/stack:latest\n" +
344+
" - git://github.com/user/repo.git\n" +
345+
"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
346+
"Do you want to continue? [y/N]: ",
347+
},
348+
{
349+
name: "user rejects remote includes",
350+
opts: buildOptions{
351+
ProjectOptions: &ProjectOptions{
352+
ConfigPaths: []string{
353+
"oci://registry.example.com/stack:latest",
354+
},
355+
},
356+
},
357+
assumeYes: false,
358+
userInput: "n\n",
359+
wantErr: true,
360+
errMessage: "operation cancelled by user",
361+
wantPrompt: true,
362+
wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" +
363+
" - oci://registry.example.com/stack:latest\n" +
364+
"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
365+
"Do you want to continue? [y/N]: ",
366+
},
367+
}
368+
369+
buf := new(bytes.Buffer)
370+
for _, tt := range tests {
371+
t.Run(tt.name, func(t *testing.T) {
372+
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
373+
374+
if tt.wantPrompt {
375+
inbuf := io.NopCloser(bytes.NewBufferString(tt.userInput))
376+
cli.EXPECT().In().Return(streams.NewIn(inbuf)).AnyTimes()
377+
}
378+
379+
err := confirmRemoteIncludes(cli, tt.opts, tt.assumeYes)
380+
381+
if tt.wantErr {
382+
require.Error(t, err)
383+
require.Equal(t, tt.errMessage, err.Error())
384+
} else {
385+
require.NoError(t, err)
386+
}
387+
388+
if tt.wantOutput != "" {
389+
require.Equal(t, tt.wantOutput, buf.String())
390+
}
391+
buf.Reset()
392+
})
393+
}
394+
}

pkg/prompt/prompt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,6 @@ type Pipe struct {
9696
func (u Pipe) Confirm(message string, defaultValue bool) (bool, error) {
9797
_, _ = fmt.Fprint(u.stdout, message)
9898
var answer string
99-
_, _ = fmt.Scanln(&answer)
99+
_, _ = fmt.Fscanln(u.stdin, &answer)
100100
return utils.StringToBool(answer), nil
101101
}

0 commit comments

Comments
 (0)