diff --git a/src/k8s/api/v1/types.go b/src/k8s/api/v1/types.go index 7fde7a440..7c53a68a8 100644 --- a/src/k8s/api/v1/types.go +++ b/src/k8s/api/v1/types.go @@ -19,6 +19,7 @@ type BootstrapConfig struct { EnableRBAC *bool `yaml:"enable-rbac"` K8sDqlitePort int `yaml:"k8s-dqlite-port"` Datastore string `yaml:"datastore"` + ExtraSANs string `yaml:"extrasans"` DatastoreURL string `yaml:"datastore-url,omitempty"` DatastoreCACert string `yaml:"datastore-ca-crt,omitempty"` DatastoreClientCert string `yaml:"datastore-client-crt,omitempty"` @@ -33,6 +34,7 @@ func (b *BootstrapConfig) SetDefaults() { b.EnableRBAC = vals.Pointer(true) b.K8sDqlitePort = 9000 b.Datastore = "k8s-dqlite" + b.ExtraSANs = "" } // ToMap marshals the BootstrapConfig into yaml and map it to "bootstrapConfig". diff --git a/src/k8s/cmd/k8s/k8s_bootstrap.go b/src/k8s/cmd/k8s/k8s_bootstrap.go index 42daff604..dd13ce975 100644 --- a/src/k8s/cmd/k8s/k8s_bootstrap.go +++ b/src/k8s/cmd/k8s/k8s_bootstrap.go @@ -150,6 +150,10 @@ func getConfigInteractively(stdin io.Reader, stdout io.Writer, stderr io.Writer) config.ServiceCIDR = askQuestion(stdin, stdout, stderr, "Please set the Service CIDR:", nil, config.ServiceCIDR, nil) rbac := askBool(stdin, stdout, stderr, "Enable Role Based Access Control (RBAC)?", []string{"yes", "no"}, "yes") *config.EnableRBAC = rbac + extraSANs := askBool(stdin, stdout, stderr, "Set extra SANs for the certificates?", []string{"yes", "no"}, "no") + if extraSANs { + config.ExtraSANs = askQuestion(stdin, stdout, stderr, "Please set the Extra SANs:", nil, config.ExtraSANs, nil) + } return config } diff --git a/src/k8s/pkg/k8sd/app/hooks_bootstrap.go b/src/k8s/pkg/k8sd/app/hooks_bootstrap.go index 21857fbf7..199c3fc48 100644 --- a/src/k8s/pkg/k8sd/app/hooks_bootstrap.go +++ b/src/k8s/pkg/k8sd/app/hooks_bootstrap.go @@ -216,6 +216,7 @@ func onBootstrapControlPlane(s *state.State, initConfig map[string]string) error certificates := pki.NewControlPlanePKI(pki.ControlPlanePKIOpts{ Hostname: s.Name(), IPSANs: append([]net.IP{nodeIP}, serviceIPs...), + ExtraSANs: cfg.Certificates.ExtraSANs, Years: 20, AllowSelfSignedCA: true, IncludeMachineAddressSANs: true, diff --git a/src/k8s/pkg/k8sd/pki/control_plane.go b/src/k8s/pkg/k8sd/pki/control_plane.go index 8de224526..0c6fa0043 100644 --- a/src/k8s/pkg/k8sd/pki/control_plane.go +++ b/src/k8s/pkg/k8sd/pki/control_plane.go @@ -4,6 +4,8 @@ import ( "crypto/x509/pkix" "fmt" "net" + + "github.com/canonical/k8s/pkg/utils" ) // ControlPlanePKI is a list of all certificates we require for a control plane node. @@ -13,7 +15,8 @@ type ControlPlanePKI struct { hostname string // node name ipSANs []net.IP // IP SANs for generated certificates dnsSANs []string // DNS SANs for the certificates below - years int // how many years the generated certificates will be valid for + extraSANs []string + years int // how many years the generated certificates will be valid for CACert, CAKey string // CN=kubernetes-ca (self-signed) FrontProxyCACert, FrontProxyCAKey string // CN=kubernetes-front-proxy-ca (self-signed) @@ -34,6 +37,7 @@ type ControlPlanePKIOpts struct { Hostname string DNSSANs []string IPSANs []net.IP + ExtraSANs string Years int AllowSelfSignedCA bool IncludeMachineAddressSANs bool @@ -44,11 +48,14 @@ func NewControlPlanePKI(opts ControlPlanePKIOpts) *ControlPlanePKI { opts.Years = 1 } + userDefinedSANs := utils.GetExtraSANsFromString(opts.ExtraSANs) + userDefinedIpSANs, userDefinedDnsSANs := utils.SeparateSANs(userDefinedSANs) + return &ControlPlanePKI{ hostname: opts.Hostname, years: opts.Years, - ipSANs: opts.IPSANs, - dnsSANs: opts.DNSSANs, + ipSANs: append(opts.IPSANs, userDefinedIpSANs...), + dnsSANs: append(opts.DNSSANs, userDefinedDnsSANs...), allowSelfSignedCA: opts.AllowSelfSignedCA, includeMachineAddressSANs: opts.IncludeMachineAddressSANs, } diff --git a/src/k8s/pkg/k8sd/types/cluster_config.go b/src/k8s/pkg/k8sd/types/cluster_config.go index eec9a3a4a..c9cbfe945 100644 --- a/src/k8s/pkg/k8sd/types/cluster_config.go +++ b/src/k8s/pkg/k8sd/types/cluster_config.go @@ -39,6 +39,7 @@ type Certificates struct { APIServerKubeletClientKey string `yaml:"apiserver-kubelet-client-key,omitempty"` K8sDqliteCert string `yaml:"k8s-dqlite-crt,omitempty"` K8sDqliteKey string `yaml:"k8s-dqlite-key,omitempty"` + ExtraSANs string `yaml:"extrasans,omitempty"` DatastoreCACert string `yaml:"datastore-ca-crt,omitempty"` DatastoreClientCert string `yaml:"datastore-client-crt,omitempty"` diff --git a/src/k8s/pkg/utils/certificate.go b/src/k8s/pkg/utils/certificate.go new file mode 100644 index 000000000..33b131856 --- /dev/null +++ b/src/k8s/pkg/utils/certificate.go @@ -0,0 +1,26 @@ +package utils + +import ( + "net" + "strings" +) + +func GetExtraSANsFromString(extraSANs string) []string { + // TODO: Add validation for the extraSANs + return strings.Split(extraSANs, ",") +} + +func SeparateSANs(extraSANs []string) ([]net.IP, []string) { + var ipSANs []net.IP + var dnsSANs []string + + for _, san := range extraSANs { + if ip := net.ParseIP(san); ip != nil { + ipSANs = append(ipSANs, ip) + } else { + dnsSANs = append(dnsSANs, san) + } + } + + return ipSANs, dnsSANs +}