diff --git a/cmd/agent/app/options/options.go b/cmd/agent/app/options/options.go index 2df464b84..9c7010cda 100644 --- a/cmd/agent/app/options/options.go +++ b/cmd/agent/app/options/options.go @@ -42,7 +42,7 @@ type GrpcProxyAgentOptions struct { SyncIntervalCap time.Duration // After a duration of this time if the agent doesn't see any activity it // pings the server to see if the transport is still alive. - KeepaliveTime time.Duration + KeepaliveTime time.Duration // file contains service account authorization token for enabling proxy-server token based authorization ServiceAccountTokenPath string @@ -65,7 +65,7 @@ func (o *GrpcProxyAgentOptions) ClientSetConfig(dialOptions ...grpc.DialOption) SyncIntervalCap: o.SyncIntervalCap, DialOptions: dialOptions, ServiceAccountTokenPath: o.ServiceAccountTokenPath, - WarnOnChannelLimit: o.WarnOnChannelLimit, + WarnOnChannelLimit: o.WarnOnChannelLimit, } } diff --git a/cmd/server/app/options/options.go b/cmd/server/app/options/options.go index 7193ce9c2..fadc37dfb 100644 --- a/cmd/server/app/options/options.go +++ b/cmd/server/app/options/options.go @@ -11,6 +11,7 @@ import ( "k8s.io/klog/v2" "sigs.k8s.io/apiserver-network-proxy/pkg/server" + "sigs.k8s.io/apiserver-network-proxy/pkg/util" ) type ProxyRunOptions struct { @@ -77,6 +78,13 @@ type ProxyRunOptions struct { // blocking call has its own problems, so it cannot easily be made race condition safe. // The check is an "unlocked" read but is still use at your own peril. WarnOnChannelLimit bool + + // Cipher suites used by the server. + // If empty, the default suite will be used from tls.CipherSuites(), + // also checks if given comma separated list contains cipher from tls.InsecureCipherSuites(). + // NOTE that cipher suites are not configurable for TLS1.3, + // see: https://pkg.go.dev/crypto/tls#Config, so in that case, this option won't have any effect. + CipherSuites string } func (o *ProxyRunOptions) Flags() *pflag.FlagSet { @@ -108,6 +116,7 @@ func (o *ProxyRunOptions) Flags() *pflag.FlagSet { flags.StringVar(&o.AuthenticationAudience, "authentication-audience", o.AuthenticationAudience, "Expected agent's token authentication audience (used with agent-namespace, agent-service-account, kubeconfig).") flags.StringVar(&o.ProxyStrategies, "proxy-strategies", o.ProxyStrategies, "The list of proxy strategies used by the server to pick a backend/tunnel, available strategies are: default, destHost.") flags.BoolVar(&o.WarnOnChannelLimit, "warn-on-channel-limit", o.WarnOnChannelLimit, "Turns on a warning if the system is going to push to a full channel. The check involves an unsafe read.") + flags.StringVar(&o.CipherSuites, "cipher-suites", o.CipherSuites, "The comma separated list of allowed cipher suites. Has no effect on TLS1.3. Empty means allow default list.") return flags } @@ -139,6 +148,7 @@ func (o *ProxyRunOptions) Print() { klog.V(1).Infof("KubeconfigBurst set to %d.\n", o.KubeconfigBurst) klog.V(1).Infof("ProxyStrategies set to %q.\n", o.ProxyStrategies) klog.V(1).Infof("WarnOnChannelLimit set to %t.\n", o.WarnOnChannelLimit) + klog.V(1).Infof("CipherSuites set to %q.\n", o.CipherSuites) } func (o *ProxyRunOptions) Validate() error { @@ -268,6 +278,18 @@ func (o *ProxyRunOptions) Validate() error { } } + // validate the cipher suites + if o.CipherSuites != "" { + acceptedCiphers := util.GetAcceptedCiphers() + css := strings.Split(o.CipherSuites, ",") + for _, cipher := range css { + _, ok := acceptedCiphers[cipher] + if !ok { + return fmt.Errorf("cipher suite %s not supported, doesn't exist or considered as insecure", cipher) + } + } + } + return nil } @@ -300,6 +322,7 @@ func NewProxyRunOptions() *ProxyRunOptions { AuthenticationAudience: "", ProxyStrategies: "default", WarnOnChannelLimit: false, + CipherSuites: "", } return &o } diff --git a/cmd/server/app/server.go b/cmd/server/app/server.go index 61528a6d7..9e2cc8f46 100644 --- a/cmd/server/app/server.go +++ b/cmd/server/app/server.go @@ -13,6 +13,7 @@ import ( "os/signal" "path/filepath" "runtime" + "strings" "sync" "syscall" @@ -46,6 +47,20 @@ func NewProxyCommand(p *Proxy, o *options.ProxyRunOptions) *cobra.Command { return cmd } +func tlsCipherSuites(cipherNames []string) []uint16 { + // return nil, so use default cipher list + if len(cipherNames) == 0 { + return nil + } + + acceptedCiphers := util.GetAcceptedCiphers() + ciphersIntSlice := make([]uint16, 0) + for _, cipher := range cipherNames { + ciphersIntSlice = append(ciphersIntSlice, acceptedCiphers[cipher]) + } + return ciphersIntSlice +} + type Proxy struct { } @@ -211,14 +226,16 @@ func (p *Proxy) runUDSFrontendServer(ctx context.Context, o *options.ProxyRunOpt return stop, nil } -func (p *Proxy) getTLSConfig(caFile, certFile, keyFile string) (*tls.Config, error) { +func (p *Proxy) getTLSConfig(caFile, certFile, keyFile, cipherSuites string) (*tls.Config, error) { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, fmt.Errorf("failed to load X509 key pair %s and %s: %v", certFile, keyFile, err) } + cipherSuiteIDs := tlsCipherSuites(strings.Split(cipherSuites, ",")) + if caFile == "" { - return &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12}, nil + return &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12, CipherSuites: cipherSuiteIDs}, nil } certPool := x509.NewCertPool() @@ -230,11 +247,13 @@ func (p *Proxy) getTLSConfig(caFile, certFile, keyFile string) (*tls.Config, err if !ok { return nil, fmt.Errorf("failed to append cluster CA cert to the cert pool") } + tlsConfig := &tls.Config{ ClientAuth: tls.RequireAndVerifyClientCert, Certificates: []tls.Certificate{cert}, ClientCAs: certPool, MinVersion: tls.VersionTLS12, + CipherSuites: cipherSuiteIDs, } return tlsConfig, nil @@ -245,7 +264,7 @@ func (p *Proxy) runMTLSFrontendServer(ctx context.Context, o *options.ProxyRunOp var tlsConfig *tls.Config var err error - if tlsConfig, err = p.getTLSConfig(o.ServerCaCert, o.ServerCert, o.ServerKey); err != nil { + if tlsConfig, err = p.getTLSConfig(o.ServerCaCert, o.ServerCert, o.ServerKey, o.CipherSuites); err != nil { return nil, err } @@ -294,7 +313,7 @@ func (p *Proxy) runMTLSFrontendServer(ctx context.Context, o *options.ProxyRunOp func (p *Proxy) runAgentServer(o *options.ProxyRunOptions, server *server.ProxyServer) error { var tlsConfig *tls.Config var err error - if tlsConfig, err = p.getTLSConfig(o.ClusterCaCert, o.ClusterCert, o.ClusterKey); err != nil { + if tlsConfig, err = p.getTLSConfig(o.ClusterCaCert, o.ClusterCert, o.ClusterKey, o.CipherSuites); err != nil { return err } diff --git a/go.mod b/go.mod index 74ea98e22..e943c1c18 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/golang/mock v1.4.4 github.com/golang/protobuf v1.4.3 github.com/google/uuid v1.1.2 - github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/prometheus/client_golang v1.7.1 github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.5 @@ -30,6 +29,7 @@ require ( github.com/google/gofuzz v1.1.0 // indirect github.com/googleapis/gnostic v0.4.1 // indirect github.com/imdario/mergo v0.3.5 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/json-iterator/go v1.1.10 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/pkg/agent/clientset.go b/pkg/agent/clientset.go index f9efdda1b..23a613342 100644 --- a/pkg/agent/clientset.go +++ b/pkg/agent/clientset.go @@ -137,7 +137,7 @@ func (cc *ClientSetConfig) NewAgentClientSet(stopCh <-chan struct{}) *ClientSet syncIntervalCap: cc.SyncIntervalCap, dialOptions: cc.DialOptions, serviceAccountTokenPath: cc.ServiceAccountTokenPath, - warnOnChannelLimit: cc.WarnOnChannelLimit, + warnOnChannelLimit: cc.WarnOnChannelLimit, stopCh: stopCh, } } diff --git a/pkg/util/net.go b/pkg/util/net.go index 26a1d5564..a0326c7ec 100644 --- a/pkg/util/net.go +++ b/pkg/util/net.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + "crypto/tls" "strings" ) @@ -48,3 +49,12 @@ func RemovePortFromHost(host string) string { } return strings.Trim(host, "[]") } + +// GetAcceptedCiphers returns all the ciphers supported by the crypto/tls package +func GetAcceptedCiphers() map[string]uint16 { + acceptedCiphers := make(map[string]uint16, len(tls.CipherSuites())) + for _, v := range tls.CipherSuites() { + acceptedCiphers[v.Name] = v.ID + } + return acceptedCiphers +}