Skip to content

Commit b43d4d8

Browse files
authored
Merge pull request #6742 from thaJeztah/validate_detachkeys
improve validation of "--detach-keys" options
2 parents 8f50791 + fe31574 commit b43d4d8

File tree

9 files changed

+118
-33
lines changed

9 files changed

+118
-33
lines changed

cli/command/container/attach.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ func newAttachCommand(dockerCLI command.Cli) *cobra.Command {
7070

7171
// RunAttach executes an `attach` command
7272
func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, opts *AttachOptions) error {
73+
detachKeys := opts.DetachKeys
74+
if detachKeys == "" {
75+
detachKeys = dockerCLI.ConfigFile().DetachKeys
76+
}
77+
if err := validateDetachKeys(detachKeys); err != nil {
78+
return err
79+
}
80+
7381
apiClient := dockerCLI.Client()
7482

7583
// request channel to wait for client
@@ -85,11 +93,6 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
8593
return err
8694
}
8795

88-
detachKeys := dockerCLI.ConfigFile().DetachKeys
89-
if opts.DetachKeys != "" {
90-
detachKeys = opts.DetachKeys
91-
}
92-
9396
options := client.ContainerAttachOptions{
9497
Stream: true,
9598
Stdin: !opts.NoStdin && c.Config.OpenStdin,

cli/command/container/attach_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ func TestNewAttachCommandErrors(t *testing.T) {
2727
return client.ContainerInspectResult{}, errors.New("something went wrong")
2828
},
2929
},
30+
{
31+
name: "invalid-detach-keys",
32+
args: []string{"--detach-keys", "shift-b", "5cb5bb5e4a3b"},
33+
expectedError: "invalid detach keys (shift-b):",
34+
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
35+
return client.ContainerInspectResult{}, errors.New("something went wrong")
36+
},
37+
},
3038
{
3139
name: "client-stopped",
3240
args: []string{"5cb5bb5e4a3b"},

cli/command/container/exec.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,5 +248,8 @@ func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*client
248248
} else {
249249
execOptions.DetachKeys = configFile.DetachKeys
250250
}
251+
if err := validateDetachKeys(execOpts.DetachKeys); err != nil {
252+
return nil, err
253+
}
251254
return execOptions, nil
252255
}

cli/command/container/exec_test.go

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,21 @@ TWO=2
9292
},
9393
{
9494
options: withDefaultOpts(ExecOptions{Detach: true}),
95-
configFile: configfile.ConfigFile{DetachKeys: "de"},
95+
configFile: configfile.ConfigFile{DetachKeys: "ctrl-d,e"},
9696
expected: client.ExecCreateOptions{
9797
Cmd: []string{"command"},
98-
DetachKeys: "de",
98+
DetachKeys: "ctrl-d,e",
9999
},
100100
},
101101
{
102102
options: withDefaultOpts(ExecOptions{
103103
Detach: true,
104-
DetachKeys: "ab",
104+
DetachKeys: "ctrl-a,b",
105105
}),
106-
configFile: configfile.ConfigFile{DetachKeys: "de"},
106+
configFile: configfile.ConfigFile{DetachKeys: "ctrl-d,e"},
107107
expected: client.ExecCreateOptions{
108108
Cmd: []string{"command"},
109-
DetachKeys: "ab",
109+
DetachKeys: "ctrl-a,b",
110110
},
111111
},
112112
{
@@ -147,13 +147,23 @@ TWO=2
147147
}
148148
}
149149

150-
func TestParseExecNoSuchFile(t *testing.T) {
151-
execOpts := withDefaultOpts(ExecOptions{})
152-
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
153-
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
154-
assert.ErrorContains(t, err, "no-such-env-file")
155-
assert.Check(t, os.IsNotExist(err))
156-
assert.Check(t, execConfig == nil)
150+
func TestParseExecErrors(t *testing.T) {
151+
t.Run("missing env-file", func(t *testing.T) {
152+
execOpts := withDefaultOpts(ExecOptions{})
153+
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
154+
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
155+
assert.ErrorContains(t, err, "no-such-env-file")
156+
assert.Check(t, os.IsNotExist(err))
157+
assert.Check(t, execConfig == nil)
158+
})
159+
t.Run("invalid detach keys", func(t *testing.T) {
160+
execOpts := withDefaultOpts(ExecOptions{
161+
DetachKeys: "shift-a",
162+
})
163+
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
164+
assert.Check(t, is.ErrorContains(err, "invalid detach keys (shift-a):"))
165+
assert.Check(t, is.Nil(execConfig))
166+
})
157167
}
158168

159169
func TestRunExec(t *testing.T) {

cli/command/container/hijack.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ func (r *readCloserWrapper) Close() error {
3030
return r.closer()
3131
}
3232

33+
func validateDetachKeys(keys string) error {
34+
if keys == "" {
35+
return nil
36+
}
37+
if _, err := term.ToBytes(keys); err != nil {
38+
return invalidParameter(fmt.Errorf("invalid detach keys (%s): %w", keys, err))
39+
}
40+
return nil
41+
}
42+
3343
// A hijackedIOStreamer handles copying input to and output from streams to the
3444
// connection.
3545
type hijackedIOStreamer struct {
@@ -82,13 +92,15 @@ func (h *hijackedIOStreamer) stream(ctx context.Context) error {
8292
}
8393
}
8494

85-
func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
95+
func (h *hijackedIOStreamer) setupInput() (restore func(), _ error) {
8696
if h.inputStream == nil || !h.tty {
8797
// No need to setup input TTY.
8898
// The restore func is a nop.
8999
return func() {}, nil
90100
}
91-
101+
if err := validateDetachKeys(h.detachKeys); err != nil {
102+
return nil, err
103+
}
92104
if err := setRawTerminal(h.streams); err != nil {
93105
return nil, fmt.Errorf("unable to set IO streams as raw terminal: %s", err)
94106
}
@@ -103,11 +115,11 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
103115
// Use default escape keys if an invalid sequence is given.
104116
escapeKeys := defaultEscapeKeys
105117
if h.detachKeys != "" {
106-
customEscapeKeys, err := term.ToBytes(h.detachKeys)
118+
var err error
119+
escapeKeys, err = term.ToBytes(h.detachKeys)
107120
if err != nil {
108-
logrus.Warnf("invalid detach escape keys, using default: %s", err)
109-
} else {
110-
escapeKeys = customEscapeKeys
121+
restore()
122+
return nil, err
111123
}
112124
}
113125

cli/command/container/run.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
140140
config.StdinOnce = false
141141
}
142142

143+
detachKeys := runOpts.detachKeys
144+
if detachKeys == "" {
145+
detachKeys = dockerCli.ConfigFile().DetachKeys
146+
}
147+
if err := validateDetachKeys(runOpts.detachKeys); err != nil {
148+
return err
149+
}
150+
143151
containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions)
144152
if err != nil {
145153
return toStatusError(err)
@@ -172,11 +180,6 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
172180
}()
173181
}
174182
if attach {
175-
detachKeys := dockerCli.ConfigFile().DetachKeys
176-
if runOpts.detachKeys != "" {
177-
detachKeys = runOpts.detachKeys
178-
}
179-
180183
// ctx should not be cancellable here, as this would kill the stream to the container
181184
// and we want to keep the stream open until the process in the container exits or until
182185
// the user forcefully terminates the CLI.

cli/command/container/run_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ func TestRunValidateFlags(t *testing.T) {
3434
args: []string{"--attach", "stdin", "--detach", "myimage"},
3535
expectedErr: "conflicting options: cannot specify both --attach and --detach",
3636
},
37+
{
38+
name: "with invalid --detach-keys",
39+
args: []string{"--detach-keys", "shift-a", "myimage"},
40+
expectedErr: "invalid detach keys (shift-a):",
41+
},
3742
} {
3843
t.Run(tc.name, func(t *testing.T) {
3944
cmd := newRunCommand(test.NewFakeCli(&fakeClient{}))

cli/command/container/start.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
7070
ctx, cancelFun := context.WithCancel(ctx)
7171
defer cancelFun()
7272

73+
detachKeys := opts.DetachKeys
74+
if detachKeys == "" {
75+
detachKeys = dockerCli.ConfigFile().DetachKeys
76+
}
77+
if err := validateDetachKeys(detachKeys); err != nil {
78+
return err
79+
}
80+
7381
switch {
7482
case opts.Attach || opts.OpenStdin:
7583
// We're going to attach to a container.
@@ -93,11 +101,6 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
93101
defer signal.StopCatch(sigc)
94102
}
95103

96-
detachKeys := dockerCli.ConfigFile().DetachKeys
97-
if opts.DetachKeys != "" {
98-
detachKeys = opts.DetachKeys
99-
}
100-
101104
options := client.ContainerAttachOptions{
102105
Stream: true,
103106
Stdin: opts.OpenStdin && c.Container.Config.OpenStdin,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package container
2+
3+
import (
4+
"io"
5+
"testing"
6+
7+
"github.com/docker/cli/internal/test"
8+
"gotest.tools/v3/assert"
9+
is "gotest.tools/v3/assert/cmp"
10+
)
11+
12+
func TestStartValidateFlags(t *testing.T) {
13+
for _, tc := range []struct {
14+
name string
15+
args []string
16+
expectedErr string
17+
}{
18+
{
19+
name: "with invalid --detach-keys",
20+
args: []string{"--detach-keys", "shift-a", "myimage"},
21+
expectedErr: "invalid detach keys (shift-a):",
22+
},
23+
} {
24+
t.Run(tc.name, func(t *testing.T) {
25+
cmd := newStartCommand(test.NewFakeCli(&fakeClient{}))
26+
cmd.SetOut(io.Discard)
27+
cmd.SetErr(io.Discard)
28+
cmd.SetArgs(tc.args)
29+
30+
err := cmd.Execute()
31+
if tc.expectedErr != "" {
32+
assert.Check(t, is.ErrorContains(err, tc.expectedErr))
33+
} else {
34+
assert.Check(t, is.Nil(err))
35+
}
36+
})
37+
}
38+
}

0 commit comments

Comments
 (0)