From 2cf15800803989beb38718a742427d747b08b958 Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Tue, 5 Nov 2024 12:04:15 +0100 Subject: [PATCH] Add TLS support for `crictl` `exec`, `portforward` and `attach` Add flags to allow using the TLS based streaming configuration. Signed-off-by: Sascha Grunert --- cmd/crictl/attach.go | 33 ++++++++++++++++--- cmd/crictl/exec.go | 68 +++++++++++++++++++++++++++++++++------ cmd/crictl/portforward.go | 30 +++++++++++++++-- cmd/crictl/util.go | 8 +++++ 4 files changed, 123 insertions(+), 16 deletions(-) diff --git a/cmd/crictl/attach.go b/cmd/crictl/attach.go index 4df693dc39..9b41d99a14 100644 --- a/cmd/crictl/attach.go +++ b/cmd/crictl/attach.go @@ -50,6 +50,24 @@ var runtimeAttachCommand = &cli.Command{ Value: transportSpdy, Usage: fmt.Sprintf("Transport protocol to use, one of: %s|%s", transportSpdy, transportWebsocket), }, + &cli.StringFlag{ + Name: flagTLSSNI, + Usage: "Server name used in the TLS client to check server certificates against", + Aliases: []string{"tls-server-name"}, + Value: "localhost", + }, + &cli.StringFlag{ + Name: flagTLSCA, + Usage: "Path to the streaming TLS CA certificate", + }, + &cli.StringFlag{ + Name: flagTLSCert, + Usage: "Path to the streaming TLS certificate", + }, + &cli.StringFlag{ + Name: flagTLSKey, + Usage: "Path to the streaming TLS key", + }, }, Action: func(c *cli.Context) error { id := c.Args().First() @@ -70,10 +88,17 @@ var runtimeAttachCommand = &cli.Command{ defer cancel() opts := attachOptions{ - id: id, - tty: c.Bool("tty"), - stdin: c.Bool("stdin"), + id: id, + tty: c.Bool("tty"), + stdin: c.Bool("stdin"), + transport: c.String("transport"), + } + + opts.tlsConfig, err = tlsConfigFromFlags(c) + if err != nil { + return fmt.Errorf("get TLS config from flags: %w", err) } + if err = Attach(ctx, runtimeClient, opts); err != nil { return fmt.Errorf("attaching running container failed: %w", err) } @@ -116,5 +141,5 @@ func Attach(ctx context.Context, client internalapi.RuntimeService, opts attachO } logrus.Debugf("Attach URL: %v", URL) - return stream(ctx, opts.stdin, opts.tty, opts.transport, URL) + return stream(ctx, opts.stdin, opts.tty, opts.transport, URL, opts.tlsConfig) } diff --git a/cmd/crictl/exec.go b/cmd/crictl/exec.go index 4259758752..e37b195d89 100644 --- a/cmd/crictl/exec.go +++ b/cmd/crictl/exec.go @@ -125,6 +125,24 @@ var runtimeExecCommand = &cli.Command{ Aliases: []string{"x"}, Usage: "Run the command in parallel if multiple containers are selected", }, + &cli.StringFlag{ + Name: flagTLSSNI, + Usage: "Server name used in the TLS client to check server certificates against", + Aliases: []string{"tls-server-name"}, + Value: "localhost", + }, + &cli.StringFlag{ + Name: flagTLSCA, + Usage: "Path to the streaming TLS CA certificate", + }, + &cli.StringFlag{ + Name: flagTLSCert, + Usage: "Path to the streaming TLS certificate", + }, + &cli.StringFlag{ + Name: flagTLSKey, + Usage: "Path to the streaming TLS key", + }, }, Action: func(c *cli.Context) error { if c.NArg() < 1 { @@ -200,6 +218,11 @@ var runtimeExecCommand = &cli.Command{ transport: c.String(transportFlag), } + opts.tlsConfig, err = tlsConfigFromFlags(c) + if err != nil { + return fmt.Errorf("get TLS config from flags: %w", err) + } + funcs := []func() error{} for _, id := range ids { funcs = append(funcs, func() error { @@ -210,7 +233,7 @@ var runtimeExecCommand = &cli.Command{ fmt.Println(id + ":") } if c.Bool("sync") { - exitCode, err := ExecSync(runtimeClient, optsCopy) + exitCode, err := ExecSync(runtimeClient, &optsCopy) if err != nil { return fmt.Errorf("execing command in container %s synchronously: %w", id, err) } @@ -220,7 +243,7 @@ var runtimeExecCommand = &cli.Command{ } else { ctx, cancel := context.WithCancel(c.Context) defer cancel() - err = Exec(ctx, runtimeClient, optsCopy) + err = Exec(ctx, runtimeClient, &optsCopy) if err != nil { return fmt.Errorf("execing command in container %s: %w", id, err) } @@ -241,10 +264,37 @@ var runtimeExecCommand = &cli.Command{ }, } +const ( + flagTLSSNI = "tls-sni" + flagTLSCA = "tls-ca" + flagTLSCert = "tls-cert" + flagTLSKey = "tls-key" +) + +func tlsConfigFromFlags(ctx *cli.Context) (*rest.TLSClientConfig, error) { + cfg := &rest.TLSClientConfig{ + ServerName: ctx.String(flagTLSSNI), + CAFile: ctx.String(flagTLSCA), + CertFile: ctx.String(flagTLSCert), + KeyFile: ctx.String(flagTLSKey), + } + if cfg.CAFile == "" && cfg.CertFile == "" && cfg.KeyFile == "" { + return &rest.TLSClientConfig{Insecure: true}, nil + } + if cfg.CAFile == "" || cfg.CertFile == "" || cfg.KeyFile == "" { + return nil, fmt.Errorf( + "all three flags --%s, --%s and --%s are required for TLS streaming", + flagTLSCA, flagTLSCert, flagTLSKey, + ) + } + + return cfg, nil +} + // ExecSync sends an ExecSyncRequest to the server, and parses // the returned ExecSyncResponse. The function returns the corresponding exit // code beside an general error. -func ExecSync(client internalapi.RuntimeService, opts execOptions) (int, error) { +func ExecSync(client internalapi.RuntimeService, opts *execOptions) (int, error) { request := &pb.ExecSyncRequest{ ContainerId: opts.id, Cmd: opts.cmd, @@ -271,7 +321,7 @@ func ExecSync(client internalapi.RuntimeService, opts execOptions) (int, error) } // Exec sends an ExecRequest to server, and parses the returned ExecResponse. -func Exec(ctx context.Context, client internalapi.RuntimeService, opts execOptions) error { +func Exec(ctx context.Context, client internalapi.RuntimeService, opts *execOptions) error { request := &pb.ExecRequest{ ContainerId: opts.id, Cmd: opts.cmd, @@ -305,11 +355,11 @@ func Exec(ctx context.Context, client internalapi.RuntimeService, opts execOptio } logrus.Debugf("Exec URL: %v", URL) - return stream(ctx, opts.stdin, opts.tty, opts.transport, URL) + return stream(ctx, opts.stdin, opts.tty, opts.transport, URL, opts.tlsConfig) } -func stream(ctx context.Context, in, tty bool, transport string, parsedURL *url.URL) error { - executor, err := getExecutor(transport, parsedURL) +func stream(ctx context.Context, in, tty bool, transport string, parsedURL *url.URL, tlsConfig *rest.TLSClientConfig) error { + executor, err := getExecutor(transport, parsedURL, tlsConfig) if err != nil { return fmt.Errorf("get executor: %w", err) } @@ -348,8 +398,8 @@ func stream(ctx context.Context, in, tty bool, transport string, parsedURL *url. return t.Safe(func() error { return executor.StreamWithContext(ctx, streamOptions) }) } -func getExecutor(transport string, parsedURL *url.URL) (exec remoteclient.Executor, err error) { - config := &rest.Config{TLSClientConfig: rest.TLSClientConfig{Insecure: true}} +func getExecutor(transport string, parsedURL *url.URL, tlsConfig *rest.TLSClientConfig) (exec remoteclient.Executor, err error) { + config := &rest.Config{TLSClientConfig: *tlsConfig} switch transport { case transportSpdy: diff --git a/cmd/crictl/portforward.go b/cmd/crictl/portforward.go index e3c5236b5c..53e465116d 100644 --- a/cmd/crictl/portforward.go +++ b/cmd/crictl/portforward.go @@ -45,6 +45,24 @@ var runtimePortForwardCommand = &cli.Command{ Value: transportSpdy, Usage: fmt.Sprintf("Transport protocol to use, one of: %s|%s", transportSpdy, transportWebsocket), }, + &cli.StringFlag{ + Name: flagTLSSNI, + Usage: "Server name used in the TLS client to check server certificates against", + Aliases: []string{"tls-server-name"}, + Value: "localhost", + }, + &cli.StringFlag{ + Name: flagTLSCA, + Usage: "Path to the streaming TLS CA certificate", + }, + &cli.StringFlag{ + Name: flagTLSCert, + Usage: "Path to the streaming TLS certificate", + }, + &cli.StringFlag{ + Name: flagTLSKey, + Usage: "Path to the streaming TLS key", + }, }, Action: func(c *cli.Context) error { if c.NArg() < 2 { @@ -61,6 +79,12 @@ var runtimePortForwardCommand = &cli.Command{ ports: c.Args().Tail(), transport: c.String(transportFlag), } + + opts.tlsConfig, err = tlsConfigFromFlags(c) + if err != nil { + return fmt.Errorf("get TLS config from flags: %w", err) + } + if err = PortForward(runtimeClient, opts); err != nil { return fmt.Errorf("port forward: %w", err) } @@ -99,7 +123,7 @@ func PortForward(client internalapi.RuntimeService, opts portforwardOptions) err } logrus.Debugf("PortForward URL: %v", parsedURL) - dialer, err := getDialer(opts.transport, parsedURL) + dialer, err := getDialer(opts.transport, parsedURL, opts.tlsConfig) if err != nil { return fmt.Errorf("get dialer: %w", err) } @@ -114,8 +138,8 @@ func PortForward(client internalapi.RuntimeService, opts portforwardOptions) err return pf.ForwardPorts() } -func getDialer(transport string, parsedURL *url.URL) (exec httpstream.Dialer, err error) { - config := &rest.Config{TLSClientConfig: rest.TLSClientConfig{Insecure: true}} +func getDialer(transport string, parsedURL *url.URL, tlsConfig *rest.TLSClientConfig) (exec httpstream.Dialer, err error) { + config := &rest.Config{TLSClientConfig: *tlsConfig} switch transport { case transportSpdy: diff --git a/cmd/crictl/util.go b/cmd/crictl/util.go index 0e04e10154..2fa69e36c3 100644 --- a/cmd/crictl/util.go +++ b/cmd/crictl/util.go @@ -36,6 +36,7 @@ import ( "google.golang.org/protobuf/protoadapt" "google.golang.org/protobuf/runtime/protoiface" utilyaml "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/rest" internalapi "k8s.io/cri-api/pkg/apis" pb "k8s.io/cri-api/pkg/apis/runtime/v1" "sigs.k8s.io/yaml" @@ -157,7 +158,10 @@ type execOptions struct { cmd []string // transport to be used transport string + // TLS configuration for streaming + tlsConfig *rest.TLSClientConfig } + type attachOptions struct { // id of container id string @@ -167,6 +171,8 @@ type attachOptions struct { stdin bool // transport to be used transport string + // TLS configuration for streaming + tlsConfig *rest.TLSClientConfig } type portforwardOptions struct { @@ -176,6 +182,8 @@ type portforwardOptions struct { ports []string // transport to be used transport string + // TLS configuration for streaming + tlsConfig *rest.TLSClientConfig } func getSortedKeys(m map[string]string) []string {