Skip to content

Commit

Permalink
Add SANs configurability for API server (#6921)
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisdoherty4 authored Nov 1, 2023
1 parent 19e4a77 commit 561cb0c
Show file tree
Hide file tree
Showing 44 changed files with 4,558 additions and 83 deletions.
6 changes: 6 additions & 0 deletions config/crd/bases/anywhere.eks.amazonaws.com_clusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ spec:
type: object
controlPlaneConfiguration:
properties:
certSans:
description: CertSANs is a slice of domain names or IPs to be
added as Subject Name Alternatives of the Kube API Servers Certificate.
items:
type: string
type: array
count:
description: Count defines the number of desired control plane
nodes. Defaults to 1.
Expand Down
21 changes: 21 additions & 0 deletions pkg/api/v1alpha1/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ var clusterConfigValidations = []func(*Cluster) error{
validateControlPlaneLabels,
validatePackageControllerConfiguration,
validateEksaVersion,
validateControlPlaneCertSANs,
}

// GetClusterConfig parses a Cluster object from a multiobject yaml file in disk
Expand Down Expand Up @@ -419,6 +420,26 @@ func validateControlPlaneEndpoint(clusterConfig *Cluster) error {
return nil
}

var domainNameRegex = regexp.MustCompile(`(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]`)

func validateControlPlaneCertSANs(cfg *Cluster) error {
var invalid []string
for _, san := range cfg.Spec.ControlPlaneConfiguration.CertSANs {
isDomain := domainNameRegex.MatchString(san)
isIP := net.ParseIP(san)

if !isDomain && isIP == nil {
invalid = append(invalid, san)
}
}

if len(invalid) > 0 {
return fmt.Errorf("invalid ControlPlaneConfiguration.CertSANs; must be an IP or domain name: [%v]", strings.Join(invalid, ", "))
}

return nil
}

func validateWorkerNodeGroups(clusterConfig *Cluster) error {
workerNodeGroupConfigs := clusterConfig.Spec.WorkerNodeGroupConfigurations
if len(workerNodeGroupConfigs) <= 0 {
Expand Down
6 changes: 5 additions & 1 deletion pkg/api/v1alpha1/cluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,9 @@ type ControlPlaneConfiguration struct {
// SkipLoadBalancerDeployment skip deploying control plane load balancer.
// Make sure your infrastructure can handle control plane load balancing when you set this field to true.
SkipLoadBalancerDeployment bool `json:"skipLoadBalancerDeployment,omitempty"`
// CertSANs is a slice of domain names or IPs to be added as Subject Name Alternatives of the
// Kube API Servers Certificate.
CertSANs []string `json:"certSans,omitempty"`
}

// MachineHealthCheck allows to configure timeouts for machine health checks. Machine Health Checks are responsible for remediating unhealthy Machines.
Expand Down Expand Up @@ -335,7 +338,8 @@ func (n *ControlPlaneConfiguration) Equal(o *ControlPlaneConfiguration) bool {
return false
}
return n.Count == o.Count && n.MachineGroupRef.Equal(o.MachineGroupRef) &&
TaintsSliceEqual(n.Taints, o.Taints) && MapEqual(n.Labels, o.Labels)
TaintsSliceEqual(n.Taints, o.Taints) && MapEqual(n.Labels, o.Labels) &&
SliceEqual(n.CertSANs, o.CertSANs)
}

type Endpoint struct {
Expand Down
61 changes: 61 additions & 0 deletions pkg/api/v1alpha1/cluster_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3169,3 +3169,64 @@ func TestCNIConfigIsManaged(t *testing.T) {
})
}
}

func TestValidateCluster(t *testing.T) {
for _, tc := range []struct {
Name string
Cluster *v1alpha1.Cluster
ExpectContains []string
}{
{
Name: "APIServerCertSAN_DomainName",
Cluster: baseCluster(func(c *v1alpha1.Cluster) {
c.Spec.ControlPlaneConfiguration.CertSANs = []string{"domain.com"}
}),
},
{
Name: "APIServerCertSAN_DomainName",
Cluster: baseCluster(func(c *v1alpha1.Cluster) {
c.Spec.ControlPlaneConfiguration.CertSANs = []string{"domain%com"}
}),
ExpectContains: []string{"domain%com"},
},
{
Name: "APIServerCertSAN_IP",
Cluster: baseCluster(func(c *v1alpha1.Cluster) {
c.Spec.ControlPlaneConfiguration.CertSANs = []string{"11.11.11.11"}
}),
},
{
Name: "APIServerCertSAN_Empty",
Cluster: baseCluster(func(c *v1alpha1.Cluster) {
c.Spec.ControlPlaneConfiguration.CertSANs = []string{""}
}),
ExpectContains: []string{""},
},
{
Name: "APIServerCertSAN_Multi",
Cluster: baseCluster(func(c *v1alpha1.Cluster) {
c.Spec.ControlPlaneConfiguration.CertSANs = []string{"11.11.11.11", "domain.com"}
}),
},
{
Name: "APIServerCertSAN_Multi",
Cluster: baseCluster(func(c *v1alpha1.Cluster) {
c.Spec.ControlPlaneConfiguration.CertSANs = []string{"11.11.11.11", "domain%com"}
}),
ExpectContains: []string{"domain%com"},
},
} {
t.Run(tc.Name, func(t *testing.T) {
g := NewWithT(t)
err := tc.Cluster.Validate()
if len(tc.ExpectContains) > 0 {
g.Expect(err).To(HaveOccurred())
for _, str := range tc.ExpectContains {
g.Expect(err.Error()).To(ContainSubstring(str))
}
} else {
g.Expect(err).To(Succeed())
}
})
}
}
5 changes: 5 additions & 0 deletions pkg/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/clusterapi/apibuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ func KubeadmControlPlane(clusterSpec *cluster.Spec, infrastructureObject APIObje
ExtraArgs: map[string]string{},
ExtraVolumes: []bootstrapv1.HostPathMount{},
},
CertSANs: clusterSpec.Cluster.Spec.ControlPlaneConfiguration.CertSANs,
},
ControllerManager: bootstrapv1.ControlPlaneComponent{
ExtraArgs: ControllerManagerArgs(clusterSpec),
Expand Down Expand Up @@ -222,6 +223,7 @@ func KubeadmConfigTemplate(clusterSpec *cluster.Spec, workerNodeGroupConfig anyw
ControlPlaneComponent: bootstrapv1.ControlPlaneComponent{
ExtraArgs: map[string]string{},
},
CertSANs: clusterSpec.Cluster.Spec.ControlPlaneConfiguration.CertSANs,
},
},
JoinConfiguration: &bootstrapv1.JoinConfiguration{
Expand Down
3 changes: 3 additions & 0 deletions pkg/clusterapi/apibuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func newApiBuilerTest(t *testing.T) apiBuilerTest {
"key1": "val1",
"key2": "val2",
},
CertSANs: []string{"foo.bar", "11.11.11.11"},
},
KubernetesVersion: "1.21",
},
Expand Down Expand Up @@ -311,6 +312,7 @@ func wantKubeadmControlPlane() *controlplanev1.KubeadmControlPlane {
ExtraArgs: map[string]string{},
ExtraVolumes: []bootstrapv1.HostPathMount{},
},
CertSANs: []string{"foo.bar", "11.11.11.11"},
},
ControllerManager: bootstrapv1.ControlPlaneComponent{
ExtraArgs: tlsCipherSuitesArgs(),
Expand Down Expand Up @@ -392,6 +394,7 @@ func wantKubeadmConfigTemplate() *bootstrapv1.KubeadmConfigTemplate {
ControlPlaneComponent: bootstrapv1.ControlPlaneComponent{
ExtraArgs: map[string]string{},
},
CertSANs: []string{"foo.bar", "11.11.11.11"},
},
},
JoinConfiguration: &bootstrapv1.JoinConfiguration{
Expand Down
3 changes: 3 additions & 0 deletions pkg/clusterapi/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func TestConfigureAWSIAMAuthInKubeadmControlPlane(t *testing.T) {
},
},
},
CertSANs: []string{"foo.bar", "11.11.11.11"},
},
ControllerManager: bootstrapv1.ControlPlaneComponent{
ExtraArgs: tlsCipherSuitesArgs(),
Expand Down Expand Up @@ -302,6 +303,7 @@ func TestConfigureOIDCInKubeadmControlPlane(t *testing.T) {
},
ExtraVolumes: []bootstrapv1.HostPathMount{},
},
CertSANs: []string{"foo.bar", "11.11.11.11"},
},
ControllerManager: bootstrapv1.ControlPlaneComponent{
ExtraArgs: tlsCipherSuitesArgs(),
Expand Down Expand Up @@ -426,6 +428,7 @@ func TestConfigurePodIamAuthInKubeadmControlPlane(t *testing.T) {
},
ExtraVolumes: []bootstrapv1.HostPathMount{},
},
CertSANs: []string{"foo.bar", "11.11.11.11"},
},
ControllerManager: bootstrapv1.ControlPlaneComponent{
ExtraArgs: tlsCipherSuitesArgs(),
Expand Down
4 changes: 4 additions & 0 deletions pkg/providers/cloudstack/config/template-cp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ spec:
imageRepository: {{.corednsRepository}}
imageTag: {{.corednsVersion}}
apiServer:
{{- with .apiServerCertSANs }}
certSANs:
{{- toYaml . | nindent 8 }}
{{- end }}
extraArgs:
cloud-provider: external
audit-policy-file: /etc/kubernetes/audit-policy.yaml
Expand Down
1 change: 1 addition & 0 deletions pkg/providers/cloudstack/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func buildTemplateMapCP(clusterSpec *cluster.Spec) (map[string]interface{}, erro
"controlPlaneEndpointHost": host,
"controlPlaneEndpointPort": port,
"controlPlaneReplicas": clusterSpec.Cluster.Spec.ControlPlaneConfiguration.Count,
"apiServerCertSANs": clusterSpec.Cluster.Spec.ControlPlaneConfiguration.CertSANs,
"kubernetesRepository": versionsBundle.KubeDistro.Kubernetes.Repository,
"kubernetesVersion": versionsBundle.KubeDistro.Kubernetes.Tag,
"etcdRepository": versionsBundle.KubeDistro.Etcd.Repository,
Expand Down
28 changes: 28 additions & 0 deletions pkg/providers/cloudstack/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,31 @@ func TestTemplateBuilderGenerateCAPISpecWorkersInvalidEndpoint(t *testing.T) {
_, err := templateBuilder.GenerateCAPISpecWorkers(clusterSpec, machineTemplateNames, kubeadmConfigTemplateNames)
g.Expect(err).To(MatchError(ContainSubstring("building template map for MD host 1.1.1.1:: is invalid: address 1.1.1.1::: too many colons in address")))
}

func TestTemplateBuilder_CertSANs(t *testing.T) {
for _, tc := range []struct {
Input string
Output string
}{
{
Input: "testdata/cluster_api_server_cert_san_domain_name.yaml",
Output: "testdata/expected_cluster_api_server_cert_san_domain_name.yaml",
},
{
Input: "testdata/cluster_api_server_cert_san_ip.yaml",
Output: "testdata/expected_cluster_api_server_cert_san_ip.yaml",
},
} {
g := NewWithT(t)
clusterSpec := test.NewFullClusterSpec(t, tc.Input)

bldr := cloudstack.NewTemplateBuilder(time.Now)

data, err := bldr.GenerateCAPISpecControlPlane(clusterSpec, func(values map[string]interface{}) {
values["controlPlaneTemplateName"] = clusterapi.ControlPlaneMachineTemplateName(clusterSpec.Cluster)
})
g.Expect(err).ToNot(HaveOccurred())

test.AssertContentToFile(t, string(data), tc.Output)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: Cluster
metadata:
name: test
namespace: test
spec:
clusterNetwork:
cniConfig:
cilium: {}
pods:
cidrBlocks:
- 192.168.0.0/16
services:
cidrBlocks:
- 10.96.0.0/12
controlPlaneConfiguration:
count: 1
endpoint:
host: 0.0.0.0
certSANs: ["foo.bar"]
machineGroupRef:
kind: CloudStackMachineConfig
name: test
datacenterRef:
kind: CloudStackDatacenterConfig
name: test
kubernetesVersion: "1.21"
---
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: CloudStackDatacenterConfig
metadata:
name: test
namespace: test
spec:
availabilityZones:
- account: "admin"
domain: "domain1"
name: "default-az-0"
credentialsRef: "global"
zone:
name: "zone1"
network:
name: "net1"
managementApiEndpoint: "http://127.16.0.1:8080/client/api"
---
apiVersion: anywhere.eks.amazonaws.com/v1alpha1
kind: CloudStackMachineConfig
metadata:
name: test
namespace: test
spec:
computeOffering:
name: "m4-large"
users:
- name: "mySshUsername"
sshAuthorizedKeys: # The key below was manually generated and not used in any production systems
- "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC1BK73XhIzjX+meUr7pIYh6RHbvI3tmHeQIXY5lv7aztN1UoX+bhPo3dwo2sfSQn5kuxgQdnxIZ/CTzy0p0GkEYVv3gwspCeurjmu0XmrdmaSGcGxCEWT/65NtvYrQtUE5ELxJ+N/aeZNlK2B7IWANnw/82913asXH4VksV1NYNduP0o1/G4XcwLLSyVFB078q/oEnmvdNIoS61j4/o36HVtENJgYr0idcBvwJdvcGxGnPaqOhx477t+kfJAa5n5dSA5wilIaoXH5i1Tf/HsTCM52L+iNCARvQzJYZhzbWI1MDQwzILtIBEQCJsl2XSqIupleY8CxqQ6jCXt2mhae+wPc3YmbO5rFvr2/EvC57kh3yDs1Nsuj8KOvD78KeeujbR8n8pScm3WDp62HFQ8lEKNdeRNj6kB8WnuaJvPnyZfvzOhwG65/9w13IBl7B1sWxbFnq2rMpm5uHVK7mAmjL0Tt8zoDhcE1YJEnp9xte3/pvmKPkST5Q/9ZtR9P5sI+02jY0fvPkPyC03j2gsPixG7rpOCwpOdbny4dcj0TDeeXJX8er+oVfJuLYz0pNWJcT2raDdFfcqvYA0B0IyNYlj5nWX4RuEcyT3qocLReWPnZojetvAG/H8XwOh7fEVGqHAKOVSnPXCSQJPl6s0H12jPJBDJMTydtYPEszl4/CeQ== [email protected]"
template:
name: "kubernetes_1_21"
diskOffering:
name: "Small"
mountPath: "/data-small"
device: "/dev/vdb"
filesystem: "ext4"
label: "data_disk"
symlinks:
/var/log/kubernetes: /data-small/var/log/kubernetes
affinityGroupIds:
- control-plane-anti-affinity
Loading

0 comments on commit 561cb0c

Please sign in to comment.