diff --git a/api/v1alpha1/clusterconfig_types.go b/api/v1alpha1/clusterconfig_types.go index e0bd7882d..9e02d363e 100644 --- a/api/v1alpha1/clusterconfig_types.go +++ b/api/v1alpha1/clusterconfig_types.go @@ -4,7 +4,9 @@ package v1alpha1 import ( + "fmt" "maps" + "strings" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,6 +31,13 @@ const ( CCMProviderNutanix = "nutanix" ) +var DefaultDockerCertSANs = []string{ + "localhost", + "127.0.0.1", + "0.0.0.0", + "host.docker.internal", +} + // +kubebuilder:object:root=true // ClusterConfig is the Schema for the clusterconfigs API. @@ -262,7 +271,10 @@ type ExtraAPIServerCertSANs []string func (ExtraAPIServerCertSANs) VariableSchema() clusterv1.VariableSchema { return clusterv1.VariableSchema{ OpenAPIV3Schema: clusterv1.JSONSchemaProps{ - Description: "Extra Subject Alternative Names for the API Server signing cert", + Description: fmt.Sprintf( + "Extra Subject Alternative Names for the API Server signing cert. For Docker %s are injected automatically.", + strings.Join(DefaultDockerCertSANs, ","), + ), Type: "array", UniqueItems: true, Items: &clusterv1.JSONSchemaProps{ diff --git a/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/docker-cluster-class.yaml b/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/docker-cluster-class.yaml index 57ff7ff09..4041688dd 100644 --- a/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/docker-cluster-class.yaml +++ b/charts/cluster-api-runtime-extensions-nutanix/defaultclusterclasses/docker-cluster-class.yaml @@ -65,12 +65,6 @@ spec: spec: kubeadmConfigSpec: clusterConfiguration: - apiServer: - certSANs: - - localhost - - 127.0.0.1 - - 0.0.0.0 - - host.docker.internal controllerManager: extraArgs: enable-hostpath-provisioner: "true" diff --git a/common/pkg/capi/utils/utils.go b/common/pkg/capi/utils/utils.go index ec499017a..fe574152c 100644 --- a/common/pkg/capi/utils/utils.go +++ b/common/pkg/capi/utils/utils.go @@ -45,3 +45,10 @@ func ManagementCluster(ctx context.Context, c client.Client) (*clusterv1.Cluster return cluster, nil } + +func GetProvider(cluster *clusterv1.Cluster) string { + if cluster == nil { + return "" + } + return cluster.GetLabels()[clusterv1.ProviderNameLabel] +} diff --git a/hack/examples/bases/docker/clusterclass/kustomization.yaml.tmpl b/hack/examples/bases/docker/clusterclass/kustomization.yaml.tmpl index 56ca10a60..f8299edbe 100644 --- a/hack/examples/bases/docker/clusterclass/kustomization.yaml.tmpl +++ b/hack/examples/bases/docker/clusterclass/kustomization.yaml.tmpl @@ -22,6 +22,11 @@ labels: patches: # Delete the patch and variable definitions. +- target: + kind: KubeadmControlPlaneTemplate + patch: |- + - op: "remove" + path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration/apiServer" - target: kind: ClusterClass patch: |- diff --git a/pkg/handlers/generic/mutation/extraapiservercertsans/inject.go b/pkg/handlers/generic/mutation/extraapiservercertsans/inject.go index 694d59519..3b5053014 100644 --- a/pkg/handlers/generic/mutation/extraapiservercertsans/inject.go +++ b/pkg/handlers/generic/mutation/extraapiservercertsans/inject.go @@ -5,9 +5,11 @@ package extraapiservercertsans import ( "context" + "slices" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" @@ -19,6 +21,7 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/patches/selectors" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables" + "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/utils" "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/clusterconfig" ) @@ -51,8 +54,8 @@ func (h *extraAPIServerCertSANsPatchHandler) Mutate( obj *unstructured.Unstructured, vars map[string]apiextensionsv1.JSON, holderRef runtimehooksv1.HolderReference, - clusterKey client.ObjectKey, - _ mutation.ClusterGetter, + _ client.ObjectKey, + clusterGetter mutation.ClusterGetter, ) error { log := ctrl.LoggerFrom(ctx).WithValues( "holderRef", holderRef, @@ -63,24 +66,34 @@ func (h *extraAPIServerCertSANsPatchHandler) Mutate( h.variableFieldPath..., ) if err != nil { + log.Error( + err, + "failed to get cluster config variable from extraAPIServerCertSANs mutation handler", + ) return err } if !found { log.V(5).Info("Extra API server cert SANs variable not defined") } - apiCertSANs := extraAPIServerCertSANsVar - if len(apiCertSANs) == 0 { - log.Info("No APIServerSANs to apply") - return nil + cluster, err := clusterGetter(ctx) + if err != nil { + log.Error( + err, + "failed to get cluster from extraAPIServerCertSANs mutation handler", + ) + return err } - + defaultAPICertSANs := getDefaultAPIServerSANs(cluster) + apiCertSANs := slices.Concat(extraAPIServerCertSANsVar, defaultAPICertSANs) + slices.Sort(apiCertSANs) + apiCertSANs = slices.Compact(apiCertSANs) log = log.WithValues( "variableName", h.variableName, "variableFieldPath", h.variableFieldPath, "variableValue", - extraAPIServerCertSANsVar, + apiCertSANs, ) return patches.MutateIfApplicable( @@ -94,9 +107,17 @@ func (h *extraAPIServerCertSANsPatchHandler) Mutate( if obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration == nil { obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{} } - obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer.CertSANs = extraAPIServerCertSANsVar - + obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer.CertSANs = apiCertSANs return nil }, ) } + +func getDefaultAPIServerSANs(cluster *clusterv1.Cluster) []string { + switch utils.GetProvider(cluster) { + case "docker": + return v1alpha1.DefaultDockerCertSANs + default: + return nil + } +} diff --git a/pkg/handlers/generic/mutation/extraapiservercertsans/inject_test.go b/pkg/handlers/generic/mutation/extraapiservercertsans/inject_test.go index 10a0f6893..ba858e257 100644 --- a/pkg/handlers/generic/mutation/extraapiservercertsans/inject_test.go +++ b/pkg/handlers/generic/mutation/extraapiservercertsans/inject_test.go @@ -4,10 +4,12 @@ package extraapiservercertsans import ( + "context" "testing" . "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -27,6 +29,11 @@ func TestExtraAPIServerCertSANsPatch(t *testing.T) { RunSpecs(t, "Extra API server certificate mutator suite") } +type testObj struct { + patchTest capitest.PatchTestDef + cluster clusterv1.Cluster +} + var _ = Describe("Generate Extra API server certificate patches", func() { patchGenerator := func() mutation.GeneratePatches { clientScheme := runtime.NewScheme() @@ -37,39 +44,108 @@ var _ = Describe("Generate Extra API server certificate patches", func() { return mutation.NewMetaGeneratePatchesHandler("", cl, NewPatch()).(mutation.GeneratePatches) } - testDefs := []capitest.PatchTestDef{ + testDefs := []testObj{ { - Name: "unset variable", + patchTest: capitest.PatchTestDef{ + Name: "extra API server cert SANs set with AWS", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + clusterconfig.MetaVariableName, + v1alpha1.ClusterConfigSpec{ + GenericClusterConfig: v1alpha1.GenericClusterConfig{ + ExtraAPIServerCertSANs: v1alpha1.ExtraAPIServerCertSANs{ + "a.b.c.example.com", + "a.b.c.example.com", + "d.e.f.example.com", + }, + }, + AWS: &v1alpha1.AWSSpec{}, + }, + ), + }, + RequestItem: request.NewKubeadmControlPlaneTemplateRequestItem(""), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration", + ValueMatcher: gomega.HaveKeyWithValue( + "apiServer", + gomega.HaveKeyWithValue( + "certSANs", + []interface{}{"a.b.c.example.com", "d.e.f.example.com"}, + ), + ), + }}, + }, + cluster: clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{ + clusterv1.ProviderNameLabel: "aws", + }, + }, + }, }, { - Name: "extra API server cert SANs set", - Vars: []runtimehooksv1.Variable{ - capitest.VariableWithValue( - clusterconfig.MetaVariableName, - v1alpha1.ExtraAPIServerCertSANs{"a.b.c.example.com", "d.e.f.example.com"}, - VariableName, - ), - }, - RequestItem: request.NewKubeadmControlPlaneTemplateRequestItem(""), - ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ - Operation: "add", - Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration", - ValueMatcher: gomega.HaveKeyWithValue( - "apiServer", - gomega.HaveKeyWithValue( - "certSANs", - []interface{}{"a.b.c.example.com", "d.e.f.example.com"}, + patchTest: capitest.PatchTestDef{ + Name: "extra API server cert SANs set with Docker", + Vars: []runtimehooksv1.Variable{ + capitest.VariableWithValue( + clusterconfig.MetaVariableName, + v1alpha1.ClusterConfigSpec{ + GenericClusterConfig: v1alpha1.GenericClusterConfig{ + ExtraAPIServerCertSANs: v1alpha1.ExtraAPIServerCertSANs{ + "a.b.c.example.com", + }, + }, + }, + ), + }, + RequestItem: request.NewKubeadmControlPlaneTemplateRequestItem(""), + ExpectedPatchMatchers: []capitest.JSONPatchMatcher{{ + Operation: "add", + Path: "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration", + ValueMatcher: gomega.HaveKeyWithValue( + "apiServer", + gomega.HaveKeyWithValue( + "certSANs", + []interface{}{ + "0.0.0.0", + "127.0.0.1", + "a.b.c.example.com", + "host.docker.internal", + "localhost", + }, + ), ), - ), - }}, + }}, + }, + cluster: clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: metav1.NamespaceDefault, + Labels: map[string]string{ + clusterv1.ProviderNameLabel: "docker", + }, + }, + }, }, } // create test node for each case for testIdx := range testDefs { tt := testDefs[testIdx] - It(tt.Name, func() { - capitest.AssertGeneratePatches(GinkgoT(), patchGenerator, &tt) + It(tt.patchTest.Name, func() { + clientScheme := runtime.NewScheme() + utilruntime.Must(clientgoscheme.AddToScheme(clientScheme)) + utilruntime.Must(clusterv1.AddToScheme(clientScheme)) + cl, err := helpers.TestEnv.GetK8sClientWithScheme(clientScheme) + gomega.Expect(err).To(gomega.BeNil()) + err = cl.Create(context.Background(), &tt.cluster) + gomega.Expect(err).To(gomega.BeNil()) + capitest.AssertGeneratePatches(GinkgoT(), patchGenerator, &tt.patchTest) + err = cl.Delete(context.Background(), &tt.cluster) + gomega.Expect(err).To(gomega.BeNil()) }) } })