diff --git a/cli/server.go b/cli/server.go index 61755840382e1..3baded2363bf6 100644 --- a/cli/server.go +++ b/cli/server.go @@ -354,7 +354,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. logger.Debug(ctx, "tracing closed", slog.Error(traceCloseErr)) }() - httpServers, err := ConfigureHTTPServers(inv, vals) + httpServers, err := ConfigureHTTPServers(logger, inv, vals) if err != nil { return xerrors.Errorf("configure http(s): %w", err) } @@ -1021,7 +1021,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. r.Verbosef(inv, "Shutting down provisioner daemon %d...", id) err := shutdownWithTimeout(provisionerDaemon.Shutdown, 5*time.Second) if err != nil { - cliui.Errorf(inv.Stderr, "Failed to shutdown provisioner daemon %d: %s\n", id, err) + cliui.Errorf(inv.Stderr, "Failed to shut down provisioner daemon %d: %s\n", id, err) return } err = provisionerDaemon.Close() @@ -1411,7 +1411,12 @@ func generateSelfSignedCertificate() (*tls.Certificate, error) { return &cert, nil } -func configureTLS(tlsMinVersion, tlsClientAuth string, tlsCertFiles, tlsKeyFiles []string, tlsClientCAFile string) (*tls.Config, error) { +// configureServerTLS returns the TLS config used for the Coderd server +// connections to clients. A logger is passed in to allow printing warning +// messages that do not block startup. +// +//nolint:revive +func configureServerTLS(ctx context.Context, logger slog.Logger, tlsMinVersion, tlsClientAuth string, tlsCertFiles, tlsKeyFiles []string, tlsClientCAFile string, ciphers []string, allowInsecureCiphers bool) (*tls.Config, error) { tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, NextProtos: []string{"h2", "http/1.1"}, @@ -1429,6 +1434,15 @@ func configureTLS(tlsMinVersion, tlsClientAuth string, tlsCertFiles, tlsKeyFiles return nil, xerrors.Errorf("unrecognized tls version: %q", tlsMinVersion) } + // A custom set of supported ciphers. + if len(ciphers) > 0 { + cipherIDs, err := configureCipherSuites(ctx, logger, ciphers, allowInsecureCiphers, tlsConfig.MinVersion, tls.VersionTLS13) + if err != nil { + return nil, err + } + tlsConfig.CipherSuites = cipherIDs + } + switch tlsClientAuth { case "none": tlsConfig.ClientAuth = tls.NoClientCert @@ -1487,6 +1501,160 @@ func configureTLS(tlsMinVersion, tlsClientAuth string, tlsCertFiles, tlsKeyFiles return tlsConfig, nil } +//nolint:revive +func configureCipherSuites(ctx context.Context, logger slog.Logger, ciphers []string, allowInsecureCiphers bool, minTLS, maxTLS uint16) ([]uint16, error) { + if minTLS > maxTLS { + return nil, xerrors.Errorf("minimum tls version (%s) cannot be greater than maximum tls version (%s)", versionName(minTLS), versionName(maxTLS)) + } + if minTLS >= tls.VersionTLS13 { + // The cipher suites config option is ignored for tls 1.3 and higher. + // So this user flag is a no-op if the min version is 1.3. + return nil, xerrors.Errorf("'--tls-ciphers' cannot be specified when using minimum tls version 1.3 or higher, %d ciphers found as input.", len(ciphers)) + } + // Configure the cipher suites which parses the strings and converts them + // to golang cipher suites. + supported, err := parseTLSCipherSuites(ciphers) + if err != nil { + return nil, xerrors.Errorf("tls ciphers: %w", err) + } + + // allVersions is all tls versions the server supports. + // We enumerate these to ensure if ciphers are configured, at least + // 1 cipher for each version exists. + allVersions := make(map[uint16]bool) + for v := minTLS; v <= maxTLS; v++ { + allVersions[v] = false + } + + var insecure []string + cipherIDs := make([]uint16, 0, len(supported)) + for _, cipher := range supported { + if cipher.Insecure { + // Always show this warning, even if they have allowInsecureCiphers + // specified. + logger.Warn(ctx, "insecure tls cipher specified for server use", slog.F("cipher", cipher.Name)) + insecure = append(insecure, cipher.Name) + } + + // This is a warning message to tell the user if they are specifying + // a cipher that does not support the tls versions they have specified. + // This makes the cipher essentially a "noop" cipher. + if !hasSupportedVersion(minTLS, maxTLS, cipher.SupportedVersions) { + versions := make([]string, 0, len(cipher.SupportedVersions)) + for _, sv := range cipher.SupportedVersions { + versions = append(versions, versionName(sv)) + } + logger.Warn(ctx, "cipher not supported for tls versions enabled, cipher will not be used", + slog.F("cipher", cipher.Name), + slog.F("cipher_supported_versions", strings.Join(versions, ",")), + slog.F("server_min_version", versionName(minTLS)), + slog.F("server_max_version", versionName(maxTLS)), + ) + } + + for _, v := range cipher.SupportedVersions { + allVersions[v] = true + } + + cipherIDs = append(cipherIDs, cipher.ID) + } + + if len(insecure) > 0 && !allowInsecureCiphers { + return nil, xerrors.Errorf("insecure tls ciphers specified, must use '--tls-allow-insecure-ciphers' to allow these: %s", strings.Join(insecure, ", ")) + } + + // This is an additional sanity check. The user can specify ciphers that + // do not cover the full range of tls versions they have specified. + // They can unintentionally break TLS for some tls configured versions. + var missedVersions []string + for version, covered := range allVersions { + if version == tls.VersionTLS13 { + continue // v1.3 ignores configured cipher suites. + } + if !covered { + missedVersions = append(missedVersions, versionName(version)) + } + } + if len(missedVersions) > 0 { + return nil, xerrors.Errorf("no tls ciphers supported for tls versions %q."+ + "Add additional ciphers, set the minimum version to 'tls13, or remove the ciphers configured and rely on the default", + strings.Join(missedVersions, ",")) + } + + return cipherIDs, nil +} + +// parseTLSCipherSuites will parse cipher suite names like 'TLS_RSA_WITH_AES_128_CBC_SHA' +// to their tls cipher suite structs. If a cipher suite that is unsupported is +// passed in, this function will return an error. +// This function can return insecure cipher suites. +func parseTLSCipherSuites(ciphers []string) ([]tls.CipherSuite, error) { + if len(ciphers) == 0 { + return nil, nil + } + + var unsupported []string + var supported []tls.CipherSuite + // A custom set of supported ciphers. + allCiphers := append(tls.CipherSuites(), tls.InsecureCipherSuites()...) + for _, cipher := range ciphers { + // For each cipher specified by the client, find the cipher in the + // list of golang supported ciphers. + var found *tls.CipherSuite + for _, supported := range allCiphers { + if strings.EqualFold(supported.Name, cipher) { + found = supported + break + } + } + + if found == nil { + unsupported = append(unsupported, cipher) + continue + } + + supported = append(supported, *found) + } + + if len(unsupported) > 0 { + return nil, xerrors.Errorf("unsupported tls ciphers specified, see https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L53-L75: %s", strings.Join(unsupported, ", ")) + } + + return supported, nil +} + +// hasSupportedVersion is a helper function that returns true if the list +// of supported versions contains a version between min and max. +// If the versions list is outside the min/max, then it returns false. +func hasSupportedVersion(min, max uint16, versions []uint16) bool { + for _, v := range versions { + if v >= min && v <= max { + // If one version is in between min/max, return true. + return true + } + } + return false +} + +// versionName is tls.VersionName in go 1.21. +// Until the switch, the function is copied locally. +func versionName(version uint16) string { + switch version { + case tls.VersionSSL30: + return "SSLv3" + case tls.VersionTLS10: + return "TLS 1.0" + case tls.VersionTLS11: + return "TLS 1.1" + case tls.VersionTLS12: + return "TLS 1.2" + case tls.VersionTLS13: + return "TLS 1.3" + default: + return fmt.Sprintf("0x%04X", version) + } +} + func configureOIDCPKI(orig *oauth2.Config, keyFile string, certFile string) (*oauthpki.Config, error) { // Read the files keyData, err := os.ReadFile(keyFile) @@ -2078,7 +2246,8 @@ func ConfigureTraceProvider( return tracerProvider, sqlDriver, closeTracing } -func ConfigureHTTPServers(inv *clibase.Invocation, cfg *codersdk.DeploymentValues) (_ *HTTPServers, err error) { +func ConfigureHTTPServers(logger slog.Logger, inv *clibase.Invocation, cfg *codersdk.DeploymentValues) (_ *HTTPServers, err error) { + ctx := inv.Context() httpServers := &HTTPServers{} defer func() { if err != nil { @@ -2154,16 +2323,20 @@ func ConfigureHTTPServers(inv *clibase.Invocation, cfg *codersdk.DeploymentValue // DEPRECATED: This redirect used to default to true. // It made more sense to have the redirect be opt-in. if inv.Environ.Get("CODER_TLS_REDIRECT_HTTP") == "true" || inv.ParsedFlags().Changed("tls-redirect-http-to-https") { - cliui.Warn(inv.Stderr, "--tls-redirect-http-to-https is deprecated, please use --redirect-to-access-url instead") + logger.Warn(ctx, "--tls-redirect-http-to-https is deprecated, please use --redirect-to-access-url instead") cfg.RedirectToAccessURL = cfg.TLS.RedirectHTTP } - tlsConfig, err := configureTLS( + tlsConfig, err := configureServerTLS( + ctx, + logger, cfg.TLS.MinVersion.String(), cfg.TLS.ClientAuth.String(), cfg.TLS.CertFiles, cfg.TLS.KeyFiles, cfg.TLS.ClientCAFile.String(), + cfg.TLS.SupportedCiphers.Value(), + cfg.TLS.AllowInsecureCiphers.Value(), ) if err != nil { return nil, xerrors.Errorf("configure tls: %w", err) diff --git a/cli/server_internal_test.go b/cli/server_internal_test.go new file mode 100644 index 0000000000000..66c9a66aeb6d6 --- /dev/null +++ b/cli/server_internal_test.go @@ -0,0 +1,171 @@ +package cli + +import ( + "bytes" + "context" + "crypto/tls" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "cdr.dev/slog" + "cdr.dev/slog/sloggers/sloghuman" +) + +func Test_configureCipherSuites(t *testing.T) { + t.Parallel() + + cipherNames := func(ciphers []*tls.CipherSuite) []string { + var names []string + for _, c := range ciphers { + names = append(names, c.Name) + } + return names + } + + cipherIDs := func(ciphers []*tls.CipherSuite) []uint16 { + var ids []uint16 + for _, c := range ciphers { + ids = append(ids, c.ID) + } + return ids + } + + cipherByName := func(cipher string) *tls.CipherSuite { + for _, c := range append(tls.CipherSuites(), tls.InsecureCipherSuites()...) { + if cipher == c.Name { + c := c + return c + } + } + return nil + } + + tests := []struct { + name string + wantErr string + wantWarnings []string + inputCiphers []string + minTLS uint16 + maxTLS uint16 + allowInsecure bool + expectCiphers []uint16 + }{ + { + name: "AllSecure", + minTLS: tls.VersionTLS10, + maxTLS: tls.VersionTLS13, + inputCiphers: cipherNames(tls.CipherSuites()), + wantWarnings: []string{}, + expectCiphers: cipherIDs(tls.CipherSuites()), + }, + { + name: "AllowInsecure", + minTLS: tls.VersionTLS10, + maxTLS: tls.VersionTLS13, + inputCiphers: append(cipherNames(tls.CipherSuites()), tls.InsecureCipherSuites()[0].Name), + allowInsecure: true, + wantWarnings: []string{ + "insecure tls cipher specified", + }, + expectCiphers: append(cipherIDs(tls.CipherSuites()), tls.InsecureCipherSuites()[0].ID), + }, + { + name: "AllInsecure", + minTLS: tls.VersionTLS10, + maxTLS: tls.VersionTLS13, + inputCiphers: append(cipherNames(tls.CipherSuites()), cipherNames(tls.InsecureCipherSuites())...), + allowInsecure: true, + wantWarnings: []string{ + "insecure tls cipher specified", + }, + expectCiphers: append(cipherIDs(tls.CipherSuites()), cipherIDs(tls.InsecureCipherSuites())...), + }, + { + // Providing ciphers that are not compatible with any tls version + // enabled should generate a warning. + name: "ExcessiveCiphers", + minTLS: tls.VersionTLS10, + maxTLS: tls.VersionTLS11, + inputCiphers: []string{ + "TLS_RSA_WITH_AES_128_CBC_SHA", + // Only for TLS 1.3 + "TLS_AES_128_GCM_SHA256", + }, + allowInsecure: true, + wantWarnings: []string{ + "cipher not supported for tls versions", + }, + expectCiphers: cipherIDs([]*tls.CipherSuite{ + cipherByName("TLS_RSA_WITH_AES_128_CBC_SHA"), + cipherByName("TLS_AES_128_GCM_SHA256"), + }), + }, + // Errors + { + name: "NotRealCiphers", + minTLS: tls.VersionTLS10, + maxTLS: tls.VersionTLS13, + inputCiphers: []string{"RSA-Fake"}, + wantErr: "unsupported tls ciphers", + }, + { + name: "NoCiphers", + minTLS: tls.VersionTLS10, + maxTLS: tls.VersionTLS13, + wantErr: "no tls ciphers supported", + }, + { + name: "InsecureNotAllowed", + minTLS: tls.VersionTLS10, + maxTLS: tls.VersionTLS13, + inputCiphers: append(cipherNames(tls.CipherSuites()), tls.InsecureCipherSuites()[0].Name), + wantErr: "insecure tls ciphers specified", + }, + { + name: "TLS1.3", + minTLS: tls.VersionTLS13, + maxTLS: tls.VersionTLS13, + inputCiphers: cipherNames(tls.CipherSuites()), + wantErr: "'--tls-ciphers' cannot be specified when using minimum tls version 1.3", + }, + { + name: "TLSUnsupported", + minTLS: tls.VersionTLS10, + maxTLS: tls.VersionTLS13, + // TLS_RSA_WITH_AES_128_GCM_SHA256 only supports tls 1.2 + inputCiphers: []string{"TLS_RSA_WITH_AES_128_GCM_SHA256"}, + wantErr: "no tls ciphers supported for tls versions", + }, + { + name: "Min>Max", + minTLS: tls.VersionTLS13, + maxTLS: tls.VersionTLS12, + wantErr: "minimum tls version (TLS 1.3) cannot be greater than maximum tls version (TLS 1.2)", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + var out bytes.Buffer + logger := slog.Make(sloghuman.Sink(&out)) + + found, err := configureCipherSuites(ctx, logger, tt.inputCiphers, tt.allowInsecure, tt.minTLS, tt.maxTLS) + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + } else { + require.NoError(t, err, "no error") + require.ElementsMatch(t, tt.expectCiphers, found, "expected ciphers") + if len(tt.wantWarnings) > 0 { + logger.Sync() + for _, w := range tt.wantWarnings { + assert.Contains(t, out.String(), w, "expected warning") + } + } + } + }) + } +} diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 68953eb14e434..8981ea5f30d8f 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -249,12 +249,21 @@ can safely ignore these settings. --tls-address host:port, $CODER_TLS_ADDRESS (default: 127.0.0.1:3443) HTTPS bind address of the server. + --tls-allow-insecure-ciphers bool, $CODER_TLS_ALLOW_INSECURE_CIPHERS (default: false) + By default, only ciphers marked as 'secure' are allowed to be used. + See + https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L82-L95. + --tls-cert-file string-array, $CODER_TLS_CERT_FILE Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file. + --tls-ciphers string-array, $CODER_TLS_CIPHERS + Specify specific TLS ciphers that allowed to be used. See + https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L53-L75. + --tls-client-auth string, $CODER_TLS_CLIENT_AUTH (default: none) Policy the server will follow for TLS Client Authentication. Accepted values are "none", "request", "require-any", "verify-if-given", or diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 31304d68e1633..a52ec496de6c1 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -83,6 +83,14 @@ networking: # Path to key for client TLS authentication. It requires a PEM-encoded file. # (default: , type: string) clientKeyFile: "" + # Specify specific TLS ciphers that allowed to be used. See + # https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L53-L75. + # (default: , type: string-array) + tlsCiphers: [] + # By default, only ciphers marked as 'secure' are allowed to be used. See + # https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L82-L95. + # (default: false, type: bool) + tlsAllowInsecureCiphers: false # Controls if the 'Strict-Transport-Security' header is set on all static file # responses. This header should only be set if the server is accessed via HTTPS. # This value is the MaxAge in seconds of the header. diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index f0f449f3b57b2..89c256ca85c48 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -9865,6 +9865,9 @@ const docTemplate = `{ "address": { "$ref": "#/definitions/clibase.HostPort" }, + "allow_insecure_ciphers": { + "type": "boolean" + }, "cert_file": { "type": "array", "items": { @@ -9897,6 +9900,12 @@ const docTemplate = `{ }, "redirect_http": { "type": "boolean" + }, + "supported_ciphers": { + "type": "array", + "items": { + "type": "string" + } } } }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 68bf8d19d2d37..ab66040190ad1 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -8901,6 +8901,9 @@ "address": { "$ref": "#/definitions/clibase.HostPort" }, + "allow_insecure_ciphers": { + "type": "boolean" + }, "cert_file": { "type": "array", "items": { @@ -8933,6 +8936,12 @@ }, "redirect_http": { "type": "boolean" + }, + "supported_ciphers": { + "type": "array", + "items": { + "type": "string" + } } } }, diff --git a/codersdk/deployment.go b/codersdk/deployment.go index c53ba8d055194..586de0774849f 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -305,16 +305,18 @@ type TelemetryConfig struct { } type TLSConfig struct { - Enable clibase.Bool `json:"enable" typescript:",notnull"` - Address clibase.HostPort `json:"address" typescript:",notnull"` - RedirectHTTP clibase.Bool `json:"redirect_http" typescript:",notnull"` - CertFiles clibase.StringArray `json:"cert_file" typescript:",notnull"` - ClientAuth clibase.String `json:"client_auth" typescript:",notnull"` - ClientCAFile clibase.String `json:"client_ca_file" typescript:",notnull"` - KeyFiles clibase.StringArray `json:"key_file" typescript:",notnull"` - MinVersion clibase.String `json:"min_version" typescript:",notnull"` - ClientCertFile clibase.String `json:"client_cert_file" typescript:",notnull"` - ClientKeyFile clibase.String `json:"client_key_file" typescript:",notnull"` + Enable clibase.Bool `json:"enable" typescript:",notnull"` + Address clibase.HostPort `json:"address" typescript:",notnull"` + RedirectHTTP clibase.Bool `json:"redirect_http" typescript:",notnull"` + CertFiles clibase.StringArray `json:"cert_file" typescript:",notnull"` + ClientAuth clibase.String `json:"client_auth" typescript:",notnull"` + ClientCAFile clibase.String `json:"client_ca_file" typescript:",notnull"` + KeyFiles clibase.StringArray `json:"key_file" typescript:",notnull"` + MinVersion clibase.String `json:"min_version" typescript:",notnull"` + ClientCertFile clibase.String `json:"client_cert_file" typescript:",notnull"` + ClientKeyFile clibase.String `json:"client_key_file" typescript:",notnull"` + SupportedCiphers clibase.StringArray `json:"supported_ciphers" typescript:",notnull"` + AllowInsecureCiphers clibase.Bool `json:"allow_insecure_ciphers" typescript:",notnull"` } type TraceConfig struct { @@ -740,6 +742,28 @@ when required by your organization's security policy.`, YAML: "clientKeyFile", Annotations: clibase.Annotations{}.Mark(annotationExternalProxies, "true"), }, + { + Name: "TLS Ciphers", + Description: "Specify specific TLS ciphers that allowed to be used. See https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L53-L75.", + Flag: "tls-ciphers", + Env: "CODER_TLS_CIPHERS", + Default: "", + Value: &c.TLS.SupportedCiphers, + Group: &deploymentGroupNetworkingTLS, + YAML: "tlsCiphers", + Annotations: clibase.Annotations{}.Mark(annotationExternalProxies, "true"), + }, + { + Name: "TLS Allow Insecure Ciphers", + Description: "By default, only ciphers marked as 'secure' are allowed to be used. See https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L82-L95.", + Flag: "tls-allow-insecure-ciphers", + Env: "CODER_TLS_ALLOW_INSECURE_CIPHERS", + Default: "false", + Value: &c.TLS.AllowInsecureCiphers, + Group: &deploymentGroupNetworkingTLS, + YAML: "tlsAllowInsecureCiphers", + Annotations: clibase.Annotations{}.Mark(annotationExternalProxies, "true"), + }, // Derp settings { Name: "DERP Server Enable", diff --git a/docs/api/general.md b/docs/api/general.md index 577781136ef61..6d000836670ea 100644 --- a/docs/api/general.md +++ b/docs/api/general.md @@ -368,6 +368,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "host": "string", "port": "string" }, + "allow_insecure_ciphers": true, "cert_file": ["string"], "client_auth": "string", "client_ca_file": "string", @@ -376,7 +377,8 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "enable": true, "key_file": ["string"], "min_version": "string", - "redirect_http": true + "redirect_http": true, + "supported_ciphers": ["string"] }, "trace": { "capture_logs": true, diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 23985414d0727..4111224c08ee3 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -2289,6 +2289,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "host": "string", "port": "string" }, + "allow_insecure_ciphers": true, "cert_file": ["string"], "client_auth": "string", "client_ca_file": "string", @@ -2297,7 +2298,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "enable": true, "key_file": ["string"], "min_version": "string", - "redirect_http": true + "redirect_http": true, + "supported_ciphers": ["string"] }, "trace": { "capture_logs": true, @@ -2658,6 +2660,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "host": "string", "port": "string" }, + "allow_insecure_ciphers": true, "cert_file": ["string"], "client_auth": "string", "client_ca_file": "string", @@ -2666,7 +2669,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "enable": true, "key_file": ["string"], "min_version": "string", - "redirect_http": true + "redirect_http": true, + "supported_ciphers": ["string"] }, "trace": { "capture_logs": true, @@ -4278,6 +4282,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "host": "string", "port": "string" }, + "allow_insecure_ciphers": true, "cert_file": ["string"], "client_auth": "string", "client_ca_file": "string", @@ -4286,24 +4291,27 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "enable": true, "key_file": ["string"], "min_version": "string", - "redirect_http": true + "redirect_http": true, + "supported_ciphers": ["string"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------------------------------------ | -------- | ------------ | ----------- | -| `address` | [clibase.HostPort](#clibasehostport) | false | | | -| `cert_file` | array of string | false | | | -| `client_auth` | string | false | | | -| `client_ca_file` | string | false | | | -| `client_cert_file` | string | false | | | -| `client_key_file` | string | false | | | -| `enable` | boolean | false | | | -| `key_file` | array of string | false | | | -| `min_version` | string | false | | | -| `redirect_http` | boolean | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------------ | ------------------------------------ | -------- | ------------ | ----------- | +| `address` | [clibase.HostPort](#clibasehostport) | false | | | +| `allow_insecure_ciphers` | boolean | false | | | +| `cert_file` | array of string | false | | | +| `client_auth` | string | false | | | +| `client_ca_file` | string | false | | | +| `client_cert_file` | string | false | | | +| `client_key_file` | string | false | | | +| `enable` | boolean | false | | | +| `key_file` | array of string | false | | | +| `min_version` | string | false | | | +| `redirect_http` | boolean | false | | | +| `supported_ciphers` | array of string | false | | | ## codersdk.TelemetryConfig diff --git a/docs/cli/server.md b/docs/cli/server.md index 9258f0f92f7e6..93a07b72f98a8 100644 --- a/docs/cli/server.md +++ b/docs/cli/server.md @@ -874,6 +874,17 @@ Two optional fields can be set in the Strict-Transport-Security header; 'include HTTPS bind address of the server. +### --tls-allow-insecure-ciphers + +| | | +| ----------- | --------------------------------------------------- | +| Type | bool | +| Environment | $CODER_TLS_ALLOW_INSECURE_CIPHERS | +| YAML | networking.tls.tlsAllowInsecureCiphers | +| Default | false | + +By default, only ciphers marked as 'secure' are allowed to be used. See https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L82-L95. + ### --tls-cert-file | | | @@ -884,6 +895,16 @@ HTTPS bind address of the server. Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file. +### --tls-ciphers + +| | | +| ----------- | -------------------------------------- | +| Type | string-array | +| Environment | $CODER_TLS_CIPHERS | +| YAML | networking.tls.tlsCiphers | + +Specify specific TLS ciphers that allowed to be used. See https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L53-L75. + ### --tls-client-auth | | | diff --git a/enterprise/cli/proxyserver.go b/enterprise/cli/proxyserver.go index 6829b155eb2fd..0d7e92531342f 100644 --- a/enterprise/cli/proxyserver.go +++ b/enterprise/cli/proxyserver.go @@ -158,7 +158,7 @@ func (*RootCmd) proxyServer() *clibase.Cmd { logger.Debug(ctx, "tracing closed", slog.Error(traceCloseErr)) }() - httpServers, err := cli.ConfigureHTTPServers(inv, cfg) + httpServers, err := cli.ConfigureHTTPServers(logger, inv, cfg) if err != nil { return xerrors.Errorf("configure http(s): %w", err) } diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden index f4bb522f5ec62..6e3b4a502ed27 100644 --- a/enterprise/cli/testdata/coder_server_--help.golden +++ b/enterprise/cli/testdata/coder_server_--help.golden @@ -250,12 +250,21 @@ can safely ignore these settings. --tls-address host:port, $CODER_TLS_ADDRESS (default: 127.0.0.1:3443) HTTPS bind address of the server. + --tls-allow-insecure-ciphers bool, $CODER_TLS_ALLOW_INSECURE_CIPHERS (default: false) + By default, only ciphers marked as 'secure' are allowed to be used. + See + https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L82-L95. + --tls-cert-file string-array, $CODER_TLS_CERT_FILE Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file. + --tls-ciphers string-array, $CODER_TLS_CIPHERS + Specify specific TLS ciphers that allowed to be used. See + https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L53-L75. + --tls-client-auth string, $CODER_TLS_CLIENT_AUTH (default: none) Policy the server will follow for TLS Client Authentication. Accepted values are "none", "request", "require-any", "verify-if-given", or diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 1485121342a32..a4f52e1f46387 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -882,6 +882,8 @@ export interface TLSConfig { readonly min_version: string; readonly client_cert_file: string; readonly client_key_file: string; + readonly supported_ciphers: string[]; + readonly allow_insecure_ciphers: boolean; } // From codersdk/deployment.go