feat: enforce TLS 1.3 minmum version for Kubernetes components

Fixes #13120

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2026-04-23 15:51:27 +04:00
parent 3db14309e0
commit 560bcf0cae
No known key found for this signature in database
GPG Key ID: 322C6F63F594CE7C
4 changed files with 46 additions and 28 deletions

View File

@ -34,6 +34,13 @@ NTS is enabled by default (without any configuration sources) for the default `t
NTS can be enabled for custom time servers via the new `useNTS` field in the `TimeServerConfig` document.
"""
[notes.tls13]
title = "TLS 1.3 Minimum Version"
description = """\
Talos now runs etcd and kube-apiserver with a minimum TLS version of 1.3, improving security by leveraging the latest TLS features and cipher suites.
Custom settings for cipher suites have been removed, as they are ignored when TLS 1.3 is used, which simplifies configuration and ensures the use of modern, secure defaults.
"""
[make_deps]

View File

@ -369,30 +369,28 @@ func (ctrl *ControlPlaneStaticPodController) manageAPIServer(ctx context.Context
"proxy-client-cert-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "front-proxy-client.crt")},
"proxy-client-key-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "front-proxy-client.key")},
"enable-bootstrap-token-auth": {"true"},
// NB: using TLS 1.2 instead of 1.3 here for interoperability, since this is an externally-facing service.
"tls-min-version": {"VersionTLS12"},
"tls-cipher-suites": {"TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"}, //nolint:lll
"encryption-provider-config": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "encryptionconfig.yaml")},
"audit-policy-file": {filepath.Join(constants.KubernetesAPIServerConfigDir, "auditpolicy.yaml")},
"audit-log-path": {filepath.Join(constants.KubernetesAuditLogDir, "kube-apiserver.log")},
"audit-log-maxage": {"30"},
"audit-log-maxbackup": {"10"},
"audit-log-maxsize": {"100"},
"profiling": {"false"},
"etcd-cafile": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "etcd-client-ca.crt")},
"etcd-certfile": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "etcd-client.crt")},
"etcd-keyfile": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "etcd-client.key")},
"etcd-servers": {strings.Join(cfg.EtcdServers, ",")},
"kubelet-client-certificate": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "apiserver-kubelet-client.crt")},
"kubelet-client-key": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "apiserver-kubelet-client.key")},
"secure-port": {strconv.FormatInt(int64(cfg.LocalPort), 10)},
"service-account-issuer": {cfg.ControlPlaneEndpoint},
"service-account-key-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "service-account.pub")},
"service-account-signing-key-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "service-account.key")},
"service-cluster-ip-range": {strings.Join(cfg.ServiceCIDRs, ",")},
"tls-cert-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "apiserver.crt")},
"tls-private-key-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "apiserver.key")},
"kubelet-preferred-address-types": {"InternalIP,ExternalIP,Hostname"},
"tls-min-version": {"VersionTLS13"},
"encryption-provider-config": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "encryptionconfig.yaml")},
"audit-policy-file": {filepath.Join(constants.KubernetesAPIServerConfigDir, "auditpolicy.yaml")},
"audit-log-path": {filepath.Join(constants.KubernetesAuditLogDir, "kube-apiserver.log")},
"audit-log-maxage": {"30"},
"audit-log-maxbackup": {"10"},
"audit-log-maxsize": {"100"},
"profiling": {"false"},
"etcd-cafile": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "etcd-client-ca.crt")},
"etcd-certfile": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "etcd-client.crt")},
"etcd-keyfile": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "etcd-client.key")},
"etcd-servers": {strings.Join(cfg.EtcdServers, ",")},
"kubelet-client-certificate": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "apiserver-kubelet-client.crt")},
"kubelet-client-key": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "apiserver-kubelet-client.key")},
"secure-port": {strconv.FormatInt(int64(cfg.LocalPort), 10)},
"service-account-issuer": {cfg.ControlPlaneEndpoint},
"service-account-key-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "service-account.pub")},
"service-account-signing-key-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "service-account.key")},
"service-cluster-ip-range": {strings.Join(cfg.ServiceCIDRs, ",")},
"tls-cert-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "apiserver.crt")},
"tls-private-key-file": {filepath.Join(constants.KubernetesAPIServerSecretsDir, "apiserver.key")},
"kubelet-preferred-address-types": {"InternalIP,ExternalIP,Hostname"},
}
if cfg.AdvertisedAddress != "" {
@ -416,7 +414,6 @@ func (ctrl *ControlPlaneStaticPodController) manageAPIServer(ctx context.Context
"enable-admission-plugins": argsbuilder.MergeAdditive,
"feature-gates": argsbuilder.MergeAdditive,
"authorization-mode": argsbuilder.MergeAdditive,
"tls-cipher-suites": argsbuilder.MergeAdditive,
"etcd-servers": argsbuilder.MergeDenied,
"client-ca-file": argsbuilder.MergeDenied,
@ -433,6 +430,7 @@ func (ctrl *ControlPlaneStaticPodController) manageAPIServer(ctx context.Context
"service-account-key-file": argsbuilder.MergeDenied,
"service-account-signing-key-file": argsbuilder.MergeDenied,
"tls-cert-file": argsbuilder.MergeDenied,
"tls-min-version": argsbuilder.MergeDenied,
"tls-private-key-file": argsbuilder.MergeDenied,
"authorization-config": argsbuilder.MergeDenied,
}

View File

@ -55,7 +55,14 @@ func (suite *ControlPlaneStaticPodSuite) TestReconcileDefaults() {
k8s.ControllerManagerID,
k8s.SchedulerID,
},
func(*k8s.StaticPod, *assert.Assertions) {},
func(staticPod *k8s.StaticPod, asrt *assert.Assertions) {
pod, err := k8sadapter.StaticPod(staticPod).Pod()
suite.Require().NoError(err)
if staticPod.Metadata().ID() == k8s.APIServerID {
asrt.Contains(pod.Spec.Containers[0].Command, "--tls-min-version=VersionTLS13")
}
},
)
}
@ -281,6 +288,12 @@ func (suite *ControlPlaneStaticPodSuite) TestReconcileExtraArgsK8s() {
},
expectError: true,
},
{
args: map[string]k8s.ArgValues{
"tls-min-version": {Values: []string{"TLS1.2"}},
},
expectError: true,
},
}
configStatus := k8s.NewConfigStatus(k8s.ControlPlaneNamespaceName, k8s.ConfigStatusStaticPodID)

View File

@ -199,8 +199,6 @@ func (e *Etcd) Runner(r runtime.Runtime) (runner.Runner, error) {
env = append(env, "ETCD_UNSUPPORTED_ARCH="+goruntime.GOARCH)
}
env = append(env, "ETCD_CIPHER_SUITES=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305") //nolint:lll
if e.learnerMemberID != 0 {
var promoteCtx context.Context
@ -406,6 +404,7 @@ func (e *Etcd) argsForInit(ctx context.Context, r runtime.Runtime, spec *etcdres
"experimental-initial-corrupt-check": {"true"},
"experimental-watch-progress-notify-interval": {"5s"},
"experimental-compact-hash-check-enabled": {"true"},
"tls-min-version": {"TLS1.3"},
}
extraArgs := make(argsbuilder.Args, len(spec.ExtraArgs))
@ -486,6 +485,7 @@ func (e *Etcd) argsForControlPlane(ctx context.Context, r runtime.Runtime, spec
"experimental-initial-corrupt-check": {"true"},
"experimental-watch-progress-notify-interval": {"5s"},
"experimental-compact-hash-check-enabled": {"true"},
"tls-min-version": {"TLS1.3"},
}
extraArgs := make(argsbuilder.Args, len(spec.ExtraArgs))