Andrey Smirnov ce8c86d640
fix: panic in osroot controller
Fixes #8753

There seems to be a problem in the machine config anyways, as
`machine.ca.crt` is missing for the worker (this should break `apid`
connectivity), but still Talos controller shouldn't enter a panic loop.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
2024-05-21 18:26:11 +04:00

201 lines
7.0 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package secrets
import (
"context"
"errors"
"fmt"
"net/netip"
"net/url"
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/controller/generic"
"github.com/cosi-project/runtime/pkg/controller/generic/transform"
"github.com/siderolabs/crypto/x509"
"github.com/siderolabs/gen/optional"
"go.uber.org/zap"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/secrets"
)
func rootMapFunc[Output generic.ResourceWithRD](output Output, requireControlPlane bool) func(cfg *config.MachineConfig) optional.Optional[Output] {
return func(cfg *config.MachineConfig) optional.Optional[Output] {
if cfg.Metadata().ID() != config.V1Alpha1ID {
return optional.None[Output]()
}
if cfg.Config().Cluster() == nil || cfg.Config().Machine() == nil {
return optional.None[Output]()
}
if requireControlPlane && !cfg.Config().Machine().Type().IsControlPlane() {
return optional.None[Output]()
}
return optional.Some(output)
}
}
// RootEtcdController manages secrets.EtcdRoot based on configuration.
type RootEtcdController = transform.Controller[*config.MachineConfig, *secrets.EtcdRoot]
// NewRootEtcdController instanciates the controller.
func NewRootEtcdController() *RootEtcdController {
return transform.NewController(
transform.Settings[*config.MachineConfig, *secrets.EtcdRoot]{
Name: "secrets.RootEtcdController",
MapMetadataOptionalFunc: rootMapFunc(secrets.NewEtcdRoot(secrets.EtcdRootID), true),
TransformFunc: func(ctx context.Context, r controller.Reader, logger *zap.Logger, cfg *config.MachineConfig, res *secrets.EtcdRoot) error {
cfgProvider := cfg.Config()
etcdSecrets := res.TypedSpec()
etcdSecrets.EtcdCA = cfgProvider.Cluster().Etcd().CA()
if etcdSecrets.EtcdCA == nil {
return errors.New("missing cluster.etcdCA secret")
}
return nil
},
},
)
}
// RootKubernetesController manages secrets.KubernetesRoot based on configuration.
type RootKubernetesController = transform.Controller[*config.MachineConfig, *secrets.KubernetesRoot]
// NewRootKubernetesController instanciates the controller.
func NewRootKubernetesController() *RootKubernetesController {
return transform.NewController(
transform.Settings[*config.MachineConfig, *secrets.KubernetesRoot]{
Name: "secrets.RootKubernetesController",
MapMetadataOptionalFunc: rootMapFunc(secrets.NewKubernetesRoot(secrets.KubernetesRootID), true),
TransformFunc: func(ctx context.Context, r controller.Reader, logger *zap.Logger, cfg *config.MachineConfig, res *secrets.KubernetesRoot) error {
cfgProvider := cfg.Config()
k8sSecrets := res.TypedSpec()
var (
err error
localEndpoint *url.URL
)
if cfgProvider.Machine().Features().KubePrism().Enabled() {
localEndpoint, err = url.Parse(fmt.Sprintf("https://127.0.0.1:%d", cfgProvider.Machine().Features().KubePrism().Port()))
if err != nil {
return err
}
} else {
localEndpoint, err = url.Parse(fmt.Sprintf("https://localhost:%d", cfgProvider.Cluster().LocalAPIServerPort()))
if err != nil {
return err
}
}
k8sSecrets.Name = cfgProvider.Cluster().Name()
k8sSecrets.Endpoint = cfgProvider.Cluster().Endpoint()
k8sSecrets.LocalEndpoint = localEndpoint
k8sSecrets.CertSANs = cfgProvider.Cluster().CertSANs()
k8sSecrets.DNSDomain = cfgProvider.Cluster().Network().DNSDomain()
k8sSecrets.APIServerIPs, err = cfgProvider.Cluster().Network().APIServerIPs()
if err != nil {
return fmt.Errorf("error building API service IPs: %w", err)
}
k8sSecrets.AggregatorCA = cfgProvider.Cluster().AggregatorCA()
if k8sSecrets.AggregatorCA == nil {
return errors.New("missing cluster.aggregatorCA secret")
}
k8sSecrets.IssuingCA = cfgProvider.Cluster().IssuingCA()
k8sSecrets.AcceptedCAs = cfgProvider.Cluster().AcceptedCAs()
if k8sSecrets.IssuingCA != nil {
k8sSecrets.AcceptedCAs = append(k8sSecrets.AcceptedCAs, &x509.PEMEncodedCertificate{
Crt: k8sSecrets.IssuingCA.Crt,
})
}
if len(k8sSecrets.IssuingCA.Key) == 0 {
// drop incomplete issuing CA, as the machine config for workers contains just the cert
k8sSecrets.IssuingCA = nil
}
if len(k8sSecrets.AcceptedCAs) == 0 {
return errors.New("missing cluster.CA secret")
}
k8sSecrets.ServiceAccount = cfgProvider.Cluster().ServiceAccount()
k8sSecrets.AESCBCEncryptionSecret = cfgProvider.Cluster().AESCBCEncryptionSecret()
k8sSecrets.SecretboxEncryptionSecret = cfgProvider.Cluster().SecretboxEncryptionSecret()
k8sSecrets.BootstrapTokenID = cfgProvider.Cluster().Token().ID()
k8sSecrets.BootstrapTokenSecret = cfgProvider.Cluster().Token().Secret()
return nil
},
},
)
}
// RootOSController manages secrets.OSRoot based on configuration.
type RootOSController = transform.Controller[*config.MachineConfig, *secrets.OSRoot]
// NewRootOSController instanciates the controller.
func NewRootOSController() *RootOSController {
return transform.NewController(
transform.Settings[*config.MachineConfig, *secrets.OSRoot]{
Name: "secrets.RootOSController",
MapMetadataOptionalFunc: rootMapFunc(secrets.NewOSRoot(secrets.OSRootID), false),
TransformFunc: func(ctx context.Context, r controller.Reader, logger *zap.Logger, cfg *config.MachineConfig, res *secrets.OSRoot) error {
cfgProvider := cfg.Config()
osSecrets := res.TypedSpec()
osSecrets.IssuingCA = cfgProvider.Machine().Security().IssuingCA()
osSecrets.AcceptedCAs = cfgProvider.Machine().Security().AcceptedCAs()
if osSecrets.IssuingCA != nil {
osSecrets.AcceptedCAs = append(osSecrets.AcceptedCAs, &x509.PEMEncodedCertificate{
Crt: osSecrets.IssuingCA.Crt,
})
if len(osSecrets.IssuingCA.Key) == 0 {
// drop incomplete issuing CA, as the machine config for workers contains just the cert
osSecrets.IssuingCA = nil
}
}
osSecrets.CertSANIPs = nil
osSecrets.CertSANDNSNames = nil
for _, san := range cfgProvider.Machine().Security().CertSANs() {
if ip, err := netip.ParseAddr(san); err == nil {
osSecrets.CertSANIPs = append(osSecrets.CertSANIPs, ip)
} else {
osSecrets.CertSANDNSNames = append(osSecrets.CertSANDNSNames, san)
}
}
if cfgProvider.Machine().Features().KubernetesTalosAPIAccess().Enabled() {
// add Kubernetes Talos service name to the list of SANs
osSecrets.CertSANDNSNames = append(osSecrets.CertSANDNSNames,
constants.KubernetesTalosAPIServiceName,
constants.KubernetesTalosAPIServiceName+"."+constants.KubernetesTalosAPIServiceNamespace,
)
}
osSecrets.Token = cfgProvider.Machine().Security().Token()
return nil
},
},
)
}