diff --git a/cmd/dockerregistry/main.go b/cmd/dockerregistry/main.go index cc19261d3..1769776f7 100644 --- a/cmd/dockerregistry/main.go +++ b/cmd/dockerregistry/main.go @@ -14,7 +14,6 @@ import ( "k8s.io/kubernetes/pkg/util/logs" "github.com/openshift/image-registry/pkg/cmd/dockerregistry" - cmdutil "github.com/openshift/origin/pkg/cmd/util" "github.com/openshift/origin/pkg/cmd/util/serviceability" ) @@ -53,12 +52,20 @@ func main() { dockerregistry.Execute(configFile) } +func env(key string, defaultValue string) string { + val := os.Getenv(key) + if len(val) == 0 { + return defaultValue + } + return val +} + func startProfiler() { - if cmdutil.Env("OPENSHIFT_PROFILE", "") == "web" { + if env("OPENSHIFT_PROFILE", "") == "web" { go func() { runtime.SetBlockProfileRate(1) - profilePort := cmdutil.Env("OPENSHIFT_PROFILE_PORT", "6060") - profileHost := cmdutil.Env("OPENSHIFT_PROFILE_HOST", "127.0.0.1") + profilePort := env("OPENSHIFT_PROFILE_PORT", "6060") + profileHost := env("OPENSHIFT_PROFILE_HOST", "127.0.0.1") log.Infof(fmt.Sprintf("Starting profiling endpoint at http://%s:%s/debug/pprof/", profileHost, profilePort)) log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%s", profileHost, profilePort), nil)) }() diff --git a/pkg/cmd/dockerregistry/dockerregistry.go b/pkg/cmd/dockerregistry/dockerregistry.go index d7baf7f27..b8e5c4cb7 100644 --- a/pkg/cmd/dockerregistry/dockerregistry.go +++ b/pkg/cmd/dockerregistry/dockerregistry.go @@ -40,16 +40,16 @@ import ( kubeversion "k8s.io/kubernetes/pkg/version" - "github.com/openshift/origin/pkg/cmd/server/crypto" "github.com/openshift/origin/pkg/version" - "github.com/openshift/image-registry/pkg/clientcmd" "github.com/openshift/image-registry/pkg/dockerregistry/server" "github.com/openshift/image-registry/pkg/dockerregistry/server/audit" "github.com/openshift/image-registry/pkg/dockerregistry/server/client" registryconfig "github.com/openshift/image-registry/pkg/dockerregistry/server/configuration" "github.com/openshift/image-registry/pkg/dockerregistry/server/maxconnections" "github.com/openshift/image-registry/pkg/dockerregistry/server/prune" + "github.com/openshift/image-registry/pkg/origin-common/clientcmd" + "github.com/openshift/image-registry/pkg/origin-common/crypto" ) var pruneMode = flag.String("prune", "", "prune blobs from the storage and exit (check, delete)") diff --git a/pkg/dockerregistry/server/auth_test.go b/pkg/dockerregistry/server/auth_test.go index 7597fca9f..f5bad9c20 100644 --- a/pkg/dockerregistry/server/auth_test.go +++ b/pkg/dockerregistry/server/auth_test.go @@ -22,10 +22,10 @@ import ( userapi "github.com/openshift/origin/pkg/user/apis/user" authorizationapi "k8s.io/kubernetes/pkg/apis/authorization/v1" - "github.com/openshift/image-registry/pkg/clientcmd" "github.com/openshift/image-registry/pkg/dockerregistry/server/client" "github.com/openshift/image-registry/pkg/dockerregistry/server/configuration" "github.com/openshift/image-registry/pkg/dockerregistry/testutil" + "github.com/openshift/image-registry/pkg/origin-common/clientcmd" ) var scheme = runtime.NewScheme() diff --git a/pkg/dockerregistry/server/client/client.go b/pkg/dockerregistry/server/client/client.go index cd76451dd..9487f8e9f 100644 --- a/pkg/dockerregistry/server/client/client.go +++ b/pkg/dockerregistry/server/client/client.go @@ -5,7 +5,7 @@ import ( authclientv1 "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/authorization/v1" kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" - "github.com/openshift/image-registry/pkg/clientcmd" + "github.com/openshift/image-registry/pkg/origin-common/clientcmd" imageclientv1 "github.com/openshift/origin/pkg/image/generated/clientset/typed/image/v1" userclientv1 "github.com/openshift/origin/pkg/user/generated/clientset/typed/user/v1" ) diff --git a/pkg/origin-common/README.md b/pkg/origin-common/README.md new file mode 100644 index 000000000..fbf00c3b1 --- /dev/null +++ b/pkg/origin-common/README.md @@ -0,0 +1,17 @@ +## Packages based on the code from the OpenShift Origin repository + +### clientcmd + +The clientcmd package is a redacted copy of [github.com/openshift/origin/pkg/cmd/util/clientcmd](https://godoc.org/github.com/openshift/origin/pkg/cmd/util/clientcmd). + +The code is almost untouched, but there are some differences: + + * some depedencies was merged into this package (getEnv, Addr, recommendedHomeFile, etc.), + * it doesn't support migrations for `KUBECONFIG` (i.e. the old default is ignored, which is `.kube/.config`), + * it uses the field `openshift.kubeconfig` from our config instead of the `--config` flag. + +### crypto + +The crypto package is a reduced copy of [github.com/openshift/origin/pkg/cmd/server/crypto](https://godoc.org/github.com/openshift/origin/pkg/cmd/server/crypto). + +We keep only functions that are required by the image registry. diff --git a/pkg/origin-common/clientcmd/addr.go b/pkg/origin-common/clientcmd/addr.go new file mode 100644 index 000000000..87919ad12 --- /dev/null +++ b/pkg/origin-common/clientcmd/addr.go @@ -0,0 +1,169 @@ +package clientcmd + +import ( + "fmt" + "net" + "net/url" + "strconv" + "strings" +) + +// urlPrefixes is the list of string prefix values that may indicate a URL +// is present. +var urlPrefixes = []string{"http://", "https://", "tcp://", "unix://"} + +// isIPv6Host returns true if the value appears to be an IPv6 host string (that does +// not include a port). +func isIPv6Host(value string) bool { + if strings.HasPrefix(value, "[") { + return false + } + return strings.Contains(value, "%") || strings.Count(value, ":") > 1 +} + +// Addr is a flag type that attempts to load a host, IP, host:port, or +// URL value from a string argument. It tracks whether the value was set +// and allows the caller to provide defaults for the scheme and port. +type Addr struct { + // Specified by the caller + DefaultScheme string + DefaultPort int + AllowPrefix bool + + // Provided will be true if Set is invoked + Provided bool + // Value is the exact value provided on the flag + Value string + + // URL represents the user input. The Host field is guaranteed + // to be set if Provided is true + URL *url.URL + // Host is the hostname or IP portion of the user input + Host string + // IPv6Host is true if the hostname appears to be an IPv6 input + IPv6Host bool + // Port is the port portion of the user input. Will be 0 if no port was found + // and no default port could be established. + Port int +} + +// Default creates a new Address with the value set +func (a Addr) Default() Addr { + if err := a.Set(a.Value); err != nil { + panic(err) + } + a.Provided = false + return a +} + +// Set attempts to set a string value to an address +func (a *Addr) Set(value string) error { + scheme := a.DefaultScheme + if len(scheme) == 0 { + scheme = "tcp" + } + addr := &url.URL{ + Scheme: scheme, + } + + switch { + case a.isURL(value): + parsed, err := url.Parse(value) + if err != nil { + return fmt.Errorf("not a valid URL: %v", err) + } + if !a.AllowPrefix { + parsed.Path = "" + } + parsed.RawQuery = "" + parsed.Fragment = "" + + if parsed.Scheme != "unix" && strings.Contains(parsed.Host, ":") { + host, port, err := net.SplitHostPort(parsed.Host) + if err != nil { + return fmt.Errorf("not a valid host:port: %v", err) + } + portNum, err := strconv.ParseUint(port, 10, 64) + if err != nil { + return fmt.Errorf("not a valid port: %v", err) + } + a.Host = host + a.Port = int(portNum) + + } else { + port := 0 + switch parsed.Scheme { + case "http": + port = 80 + case "https": + port = 443 + case "unix": + port = 0 + default: + return fmt.Errorf("no port specified") + } + a.Host = parsed.Host + a.Port = port + } + addr = parsed + + case isIPv6Host(value): + a.Host = value + a.Port = a.DefaultPort + + case strings.Contains(value, ":"): + host, port, err := net.SplitHostPort(value) + if err != nil { + return fmt.Errorf("not a valid host:port: %v", err) + } + portNum, err := strconv.ParseUint(port, 10, 64) + if err != nil { + return fmt.Errorf("not a valid port: %v", err) + } + a.Host = host + a.Port = int(portNum) + + default: + port := a.DefaultPort + if port == 0 { + switch a.DefaultScheme { + case "http": + port = 80 + case "https": + port = 443 + default: + return fmt.Errorf("no port specified") + } + } + a.Host = value + a.Port = port + } + if a.Port > 0 { + addr.Host = net.JoinHostPort(a.Host, strconv.FormatInt(int64(a.Port), 10)) + } else { + addr.Host = a.Host + } + + if value != a.Value { + a.Provided = true + } + a.URL = addr + a.IPv6Host = isIPv6Host(a.Host) + a.Value = value + + return nil +} + +// isURL returns true if the provided value appears to be a valid URL. +func (a *Addr) isURL(value string) bool { + prefixes := urlPrefixes + if a.DefaultScheme != "" { + prefixes = append(prefixes, fmt.Sprintf("%s://", a.DefaultScheme)) + } + for _, p := range prefixes { + if strings.HasPrefix(value, p) { + return true + } + } + return false +} diff --git a/pkg/clientcmd/clientcmd.go b/pkg/origin-common/clientcmd/clientcmd.go similarity index 66% rename from pkg/clientcmd/clientcmd.go rename to pkg/origin-common/clientcmd/clientcmd.go index 90cd68f4e..35cf3958a 100644 --- a/pkg/clientcmd/clientcmd.go +++ b/pkg/origin-common/clientcmd/clientcmd.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "os" + "path" "path/filepath" "strings" @@ -13,19 +14,25 @@ import ( "k8s.io/client-go/tools/clientcmd" kclientcmd "k8s.io/client-go/tools/clientcmd" kclientcmdapi "k8s.io/client-go/tools/clientcmd/api" - - "github.com/openshift/origin/pkg/cmd/flagtypes" - "github.com/openshift/origin/pkg/cmd/util" - "github.com/openshift/origin/pkg/oc/cli/config" + "k8s.io/client-go/util/homedir" ) +// getEnv returns an environment value if specified. +func getEnv(key string) (string, bool) { + val := os.Getenv(key) + if len(val) == 0 { + return "", false + } + return val, true +} + // Config contains all the necessary bits for client configuration type Config struct { // MasterAddr is the address the master can be reached on (host, host:port, or URL). - MasterAddr flagtypes.Addr + MasterAddr Addr // KubernetesAddr is the address of the Kubernetes server (host, host:port, or URL). // If omitted defaults to the master. - KubernetesAddr flagtypes.Addr + KubernetesAddr Addr // CommonConfig is the shared base config for both the OpenShift config and Kubernetes config CommonConfig restclient.Config // Namespace is the namespace to act in @@ -40,12 +47,22 @@ type Config struct { // NewConfig returns a new configuration func NewConfig() *Config { return &Config{ - MasterAddr: flagtypes.Addr{Value: "localhost:8080", DefaultScheme: "http", DefaultPort: 8080, AllowPrefix: true}.Default(), - KubernetesAddr: flagtypes.Addr{Value: "localhost:8080", DefaultScheme: "http", DefaultPort: 8080}.Default(), + MasterAddr: Addr{Value: "localhost:8080", DefaultScheme: "http", DefaultPort: 8080, AllowPrefix: true}.Default(), + KubernetesAddr: Addr{Value: "localhost:8080", DefaultScheme: "http", DefaultPort: 8080}.Default(), CommonConfig: restclient.Config{}, } } +// github.com/openshift/origin/pkg/oc/cli/config +const ( + openShiftConfigPathEnvVar = "KUBECONFIG" + openShiftConfigHomeDir = ".kube" + openShiftConfigHomeFileName = "config" + openShiftConfigHomeDirFileName = openShiftConfigHomeDir + "/" + openShiftConfigHomeFileName +) + +var recommendedHomeFile = path.Join(homedir.HomeDir(), openShiftConfigHomeDirFileName) + func (cfg *Config) BindToFile(configPath string) *Config { defaultOverrides := &kclientcmd.ConfigOverrides{ ClusterDefaults: kclientcmdapi.Cluster{ @@ -54,12 +71,12 @@ func (cfg *Config) BindToFile(configPath string) *Config { } chain := []string{} - if envVarFile := os.Getenv(config.OpenShiftConfigPathEnvVar); len(envVarFile) != 0 { + if envVarFile := os.Getenv(openShiftConfigPathEnvVar); len(envVarFile) != 0 { chain = append(chain, filepath.SplitList(envVarFile)...) } else if len(configPath) != 0 { chain = append(chain, configPath) } else { - chain = append(chain, config.RecommendedHomeFile) + chain = append(chain, recommendedHomeFile) } defaultClientConfig := kclientcmd.NewDefaultClientConfig(kclientcmdapi.Config{}, defaultOverrides) @@ -87,7 +104,7 @@ func (cfg *Config) bindEnv() error { // callers may not use the config file if they have specified a master directly, for backwards // compatibility with components that used to use env, switch to service account token, and have // config defined in env. - _, masterSet := util.GetEnv("OPENSHIFT_MASTER") + _, masterSet := getEnv("OPENSHIFT_MASTER") specifiedMaster := masterSet || cfg.MasterAddr.Provided if cfg.clientConfig != nil && !specifiedMaster { @@ -111,16 +128,16 @@ func (cfg *Config) bindEnv() error { } // Legacy path - preserve env vars set on pods that previously were honored. - if value, ok := util.GetEnv("KUBERNETES_MASTER"); ok && !cfg.KubernetesAddr.Provided { + if value, ok := getEnv("KUBERNETES_MASTER"); ok && !cfg.KubernetesAddr.Provided { cfg.KubernetesAddr.Set(value) } - if value, ok := util.GetEnv("OPENSHIFT_MASTER"); ok && !cfg.MasterAddr.Provided { + if value, ok := getEnv("OPENSHIFT_MASTER"); ok && !cfg.MasterAddr.Provided { cfg.MasterAddr.Set(value) } - if value, ok := util.GetEnv("BEARER_TOKEN"); ok && len(cfg.CommonConfig.BearerToken) == 0 { + if value, ok := getEnv("BEARER_TOKEN"); ok && len(cfg.CommonConfig.BearerToken) == 0 { cfg.CommonConfig.BearerToken = value } - if value, ok := util.GetEnv("BEARER_TOKEN_FILE"); ok && len(cfg.CommonConfig.BearerToken) == 0 { + if value, ok := getEnv("BEARER_TOKEN_FILE"); ok && len(cfg.CommonConfig.BearerToken) == 0 { if tokenData, tokenErr := ioutil.ReadFile(value); tokenErr == nil { cfg.CommonConfig.BearerToken = strings.TrimSpace(string(tokenData)) if len(cfg.CommonConfig.BearerToken) == 0 { @@ -131,25 +148,25 @@ func (cfg *Config) bindEnv() error { } } - if value, ok := util.GetEnv("OPENSHIFT_CA_FILE"); ok && len(cfg.CommonConfig.CAFile) == 0 { + if value, ok := getEnv("OPENSHIFT_CA_FILE"); ok && len(cfg.CommonConfig.CAFile) == 0 { cfg.CommonConfig.CAFile = value - } else if value, ok := util.GetEnv("OPENSHIFT_CA_DATA"); ok && len(cfg.CommonConfig.CAData) == 0 { + } else if value, ok := getEnv("OPENSHIFT_CA_DATA"); ok && len(cfg.CommonConfig.CAData) == 0 { cfg.CommonConfig.CAData = []byte(value) } - if value, ok := util.GetEnv("OPENSHIFT_CERT_FILE"); ok && len(cfg.CommonConfig.CertFile) == 0 { + if value, ok := getEnv("OPENSHIFT_CERT_FILE"); ok && len(cfg.CommonConfig.CertFile) == 0 { cfg.CommonConfig.CertFile = value - } else if value, ok := util.GetEnv("OPENSHIFT_CERT_DATA"); ok && len(cfg.CommonConfig.CertData) == 0 { + } else if value, ok := getEnv("OPENSHIFT_CERT_DATA"); ok && len(cfg.CommonConfig.CertData) == 0 { cfg.CommonConfig.CertData = []byte(value) } - if value, ok := util.GetEnv("OPENSHIFT_KEY_FILE"); ok && len(cfg.CommonConfig.KeyFile) == 0 { + if value, ok := getEnv("OPENSHIFT_KEY_FILE"); ok && len(cfg.CommonConfig.KeyFile) == 0 { cfg.CommonConfig.KeyFile = value - } else if value, ok := util.GetEnv("OPENSHIFT_KEY_DATA"); ok && len(cfg.CommonConfig.KeyData) == 0 { + } else if value, ok := getEnv("OPENSHIFT_KEY_DATA"); ok && len(cfg.CommonConfig.KeyData) == 0 { cfg.CommonConfig.KeyData = []byte(value) } - if value, ok := util.GetEnv("OPENSHIFT_INSECURE"); ok && len(value) != 0 { + if value, ok := getEnv("OPENSHIFT_INSECURE"); ok && len(value) != 0 { cfg.CommonConfig.Insecure = value == "true" } diff --git a/pkg/origin-common/crypto/crypto.go b/pkg/origin-common/crypto/crypto.go new file mode 100644 index 000000000..248c1c6c2 --- /dev/null +++ b/pkg/origin-common/crypto/crypto.go @@ -0,0 +1,125 @@ +package crypto + +import ( + "crypto/tls" + "fmt" + "sort" +) + +var versions = map[string]uint16{ + "VersionTLS10": tls.VersionTLS10, + "VersionTLS11": tls.VersionTLS11, + "VersionTLS12": tls.VersionTLS12, +} + +func TLSVersion(versionName string) (uint16, error) { + if len(versionName) == 0 { + return DefaultTLSVersion(), nil + } + if version, ok := versions[versionName]; ok { + return version, nil + } + return 0, fmt.Errorf("unknown tls version %q", versionName) +} + +func ValidTLSVersions() []string { + validVersions := []string{} + for k := range versions { + validVersions = append(validVersions, k) + } + sort.Strings(validVersions) + return validVersions +} + +func DefaultTLSVersion() uint16 { + // Can't use SSLv3 because of POODLE and BEAST + // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher + // Can't use TLSv1.1 because of RC4 cipher usage + return tls.VersionTLS12 +} + +var ciphers = map[string]uint16{ + "TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA, + "TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + "TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, + "TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, + "TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256, + "TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + "TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + "TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, +} + +func CipherSuite(cipherName string) (uint16, error) { + if cipher, ok := ciphers[cipherName]; ok { + return cipher, nil + } + return 0, fmt.Errorf("unknown cipher name %q", cipherName) +} + +func ValidCipherSuites() []string { + validCipherSuites := []string{} + for k := range ciphers { + validCipherSuites = append(validCipherSuites, k) + } + sort.Strings(validCipherSuites) + return validCipherSuites +} + +func DefaultCiphers() []uint16 { + // HTTP/2 mandates TLS 1.2 or higher with an AEAD cipher + // suite (GCM, Poly1305) and ephemeral key exchange (ECDHE, DHE) for + // perfect forward secrecy. Servers may provide additional cipher + // suites for backwards compatibility with HTTP/1.1 clients. + // See RFC7540, section 9.2 (Use of TLS Features) and Appendix A + // (TLS 1.2 Cipher Suite Black List). + return []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // required by http/2 + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, // forbidden by http/2, not flagged by http2isBadCipher() in go1.8 + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, // forbidden by http/2, not flagged by http2isBadCipher() in go1.8 + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // forbidden by http/2 + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // forbidden by http/2 + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // forbidden by http/2 + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, // forbidden by http/2 + tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // forbidden by http/2 + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, // forbidden by http/2 + // the next one is in the intermediate suite, but go1.8 http2isBadCipher() complains when it is included at the recommended index + // because it comes after ciphers forbidden by the http/2 spec + // tls.TLS_RSA_WITH_AES_128_CBC_SHA256, + // tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, // forbidden by http/2, disabled to mitigate SWEET32 attack + // tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // forbidden by http/2, disabled to mitigate SWEET32 attack + tls.TLS_RSA_WITH_AES_128_CBC_SHA, // forbidden by http/2 + tls.TLS_RSA_WITH_AES_256_CBC_SHA, // forbidden by http/2 + } +} + +// SecureTLSConfig enforces the default minimum security settings for the cluster. +func SecureTLSConfig(config *tls.Config) *tls.Config { + if config.MinVersion == 0 { + config.MinVersion = DefaultTLSVersion() + } + + config.PreferServerCipherSuites = true + if len(config.CipherSuites) == 0 { + config.CipherSuites = DefaultCiphers() + } + return config +} diff --git a/pkg/origin-common/crypto/crypto_test.go b/pkg/origin-common/crypto/crypto_test.go new file mode 100644 index 000000000..8395345a8 --- /dev/null +++ b/pkg/origin-common/crypto/crypto_test.go @@ -0,0 +1,48 @@ +package crypto + +import ( + "fmt" + "go/importer" + "strings" + "testing" +) + +func TestConstantMaps(t *testing.T) { + pkg, err := importer.Default().Import("crypto/tls") + if err != nil { + fmt.Printf("error: %s\n", err.Error()) + return + } + discoveredVersions := map[string]bool{} + discoveredCiphers := map[string]bool{} + for _, declName := range pkg.Scope().Names() { + if strings.HasPrefix(declName, "VersionTLS") { + discoveredVersions[declName] = true + } + if strings.HasPrefix(declName, "TLS_RSA_") || strings.HasPrefix(declName, "TLS_ECDHE_") { + discoveredCiphers[declName] = true + } + } + + for k := range discoveredCiphers { + if _, ok := ciphers[k]; !ok { + t.Errorf("discovered cipher tls.%s not in ciphers map", k) + } + } + for k := range ciphers { + if _, ok := discoveredCiphers[k]; !ok { + t.Errorf("ciphers map has %s not in tls package", k) + } + } + + for k := range discoveredVersions { + if _, ok := versions[k]; !ok { + t.Errorf("discovered version tls.%s not in version map", k) + } + } + for k := range versions { + if _, ok := discoveredVersions[k]; !ok { + t.Errorf("versions map has %s not in tls package", k) + } + } +}