@@ -18,7 +18,12 @@ package controllers
1818
1919import (
2020 "context"
21+ "crypto/rand"
22+ "crypto/rsa"
23+ "crypto/x509"
24+ "crypto/x509/pkix"
2125 "fmt"
26+ "math/big"
2227 "sync"
2328 "testing"
2429 "time"
@@ -49,6 +54,7 @@ import (
4954 "sigs.k8s.io/cluster-api/feature"
5055 "sigs.k8s.io/cluster-api/internal/test/builder"
5156 "sigs.k8s.io/cluster-api/util"
57+ "sigs.k8s.io/cluster-api/util/certs"
5258 "sigs.k8s.io/cluster-api/util/collections"
5359 "sigs.k8s.io/cluster-api/util/conditions"
5460 "sigs.k8s.io/cluster-api/util/kubeconfig"
@@ -520,11 +526,12 @@ func TestKubeadmControlPlaneReconciler_adoption(t *testing.T) {
520526 g .Expect (machine .GetAnnotations ()).NotTo (HaveKey (clusterv1 .TemplateClonedFromNameAnnotation ))
521527 }
522528 })
529+
523530 t .Run ("adopts v1alpha2 cluster secrets" , func (t * testing.T ) {
524531 g := NewWithT (t )
525532
526533 cluster , kcp , tmpl := createClusterWithControlPlane (metav1 .NamespaceDefault )
527- cluster .Spec .ControlPlaneEndpoint .Host = "bar "
534+ cluster .Spec .ControlPlaneEndpoint .Host = "validhost "
528535 cluster .Spec .ControlPlaneEndpoint .Port = 6443
529536 cluster .Status .InfrastructureReady = true
530537 kcp .Spec .Version = version
@@ -565,7 +572,7 @@ func TestKubeadmControlPlaneReconciler_adoption(t *testing.T) {
565572 },
566573 }
567574
568- // A simulcrum of the various Certificate and kubeconfig secrets
575+ // A simulacrum of the various Certificate and kubeconfig secrets
569576 // it's a little weird that this is one per KubeadmConfig rather than just whichever config was "first,"
570577 // but the intent is to ensure that the owner is changed regardless of which Machine we start with
571578 clusterSecret := & corev1.Secret {
@@ -749,6 +756,164 @@ func TestKubeadmControlPlaneReconciler_adoption(t *testing.T) {
749756 })
750757}
751758
759+ func TestKubeadmControlPlaneReconciler_ensureOwnerReferences (t * testing.T ) {
760+ g := NewWithT (t )
761+
762+ cluster , kcp , tmpl := createClusterWithControlPlane (metav1 .NamespaceDefault )
763+ cluster .Spec .ControlPlaneEndpoint .Host = "bar"
764+ cluster .Spec .ControlPlaneEndpoint .Port = 6443
765+ cluster .Status .InfrastructureReady = true
766+ kcp .Spec .Version = "v1.21.0"
767+ key , err := certs .NewPrivateKey ()
768+ g .Expect (err ).To (BeNil ())
769+ crt , err := getTestCACert (key )
770+ g .Expect (err ).To (BeNil ())
771+
772+ fmc := & fakeManagementCluster {
773+ Machines : collections.Machines {},
774+ Workload : fakeWorkloadCluster {},
775+ }
776+
777+ clusterSecret := & corev1.Secret {
778+ // The Secret's Type is used by KCP to determine whether it is user-provided.
779+ // clusterv1.ClusterSecretType signals that the Secret is CAPI-provided.
780+ ObjectMeta : metav1.ObjectMeta {
781+ Namespace : cluster .Namespace ,
782+ Name : "" ,
783+ Labels : map [string ]string {
784+ "cluster.x-k8s.io/cluster-name" : cluster .Name ,
785+ "testing" : "yes" ,
786+ },
787+ },
788+ Data : map [string ][]byte {
789+ secret .TLSCrtDataName : certs .EncodeCertPEM (crt ),
790+ secret .TLSKeyDataName : certs .EncodePrivateKeyPEM (key ),
791+ },
792+ }
793+
794+ t .Run ("add KCP owner for secrets with no controller reference" , func (t * testing.T ) {
795+ objs := []client.Object {fakeGenericMachineTemplateCRD , cluster .DeepCopy (), kcp .DeepCopy (), tmpl .DeepCopy ()}
796+ for _ , purpose := range []secret.Purpose {secret .ClusterCA , secret .FrontProxyCA , secret .ServiceAccount , secret .EtcdCA } {
797+ s := clusterSecret .DeepCopy ()
798+ // Set the secret name to the purpose
799+ s .Name = secret .Name (cluster .Name , purpose )
800+ // Set the Secret Type to clusterv1.ClusterSecretType which signals this Secret was generated by CAPI.
801+ s .Type = clusterv1 .ClusterSecretType
802+
803+ objs = append (objs , s )
804+ }
805+
806+ fakeClient := newFakeClient (objs ... )
807+ fmc .Reader = fakeClient
808+ r := & KubeadmControlPlaneReconciler {
809+ Client : fakeClient ,
810+ APIReader : fakeClient ,
811+ managementCluster : fmc ,
812+ managementClusterUncached : fmc ,
813+ }
814+
815+ _ , err := r .reconcile (ctx , cluster , kcp )
816+ g .Expect (err ).To (BeNil ())
817+
818+ secrets := & corev1.SecretList {}
819+ g .Expect (fakeClient .List (ctx , secrets , client .InNamespace (cluster .Namespace ), client.MatchingLabels {"testing" : "yes" })).To (Succeed ())
820+ for _ , secret := range secrets .Items {
821+ g .Expect (secret .OwnerReferences ).To (ContainElement (* metav1 .NewControllerRef (kcp , controlplanev1 .GroupVersion .WithKind ("KubeadmControlPlane" ))))
822+ }
823+ })
824+
825+ t .Run ("replace non-KCP controller with KCP controller reference" , func (t * testing.T ) {
826+ objs := []client.Object {fakeGenericMachineTemplateCRD , cluster .DeepCopy (), kcp .DeepCopy (), tmpl .DeepCopy ()}
827+ // A simulacrum of the various Certificate and kubeconfig secrets
828+ // it's a little weird that this is one per KubeadmConfig rather than just whichever config was "first,"
829+ // but the intent is to ensure that the owner is changed regardless of which Machine we start with
830+ for _ , purpose := range []secret.Purpose {secret .ClusterCA , secret .FrontProxyCA , secret .ServiceAccount , secret .EtcdCA } {
831+ s := clusterSecret .DeepCopy ()
832+ // Set the secret name to the purpose
833+ s .Name = secret .Name (cluster .Name , purpose )
834+ // Set the Secret Type to clusterv1.ClusterSecretType which signals this Secret was generated by CAPI.
835+ s .Type = clusterv1 .ClusterSecretType
836+
837+ // Set the a controller owner reference of an unknown type on the secret.
838+ s .SetOwnerReferences ([]metav1.OwnerReference {
839+ {
840+ APIVersion : bootstrapv1 .GroupVersion .String (),
841+ // KCP should take ownership of any Secret of the correct type linked to the Cluster.
842+ Kind : "OtherController" ,
843+ Name : "name" ,
844+ UID : "uid" ,
845+ Controller : pointer .Bool (true ),
846+ },
847+ })
848+ objs = append (objs , s )
849+ }
850+
851+ fakeClient := newFakeClient (objs ... )
852+ fmc .Reader = fakeClient
853+ r := & KubeadmControlPlaneReconciler {
854+ Client : fakeClient ,
855+ APIReader : fakeClient ,
856+ managementCluster : fmc ,
857+ managementClusterUncached : fmc ,
858+ }
859+
860+ _ , err := r .reconcile (ctx , cluster , kcp )
861+ g .Expect (err ).To (BeNil ())
862+
863+ secrets := & corev1.SecretList {}
864+ g .Expect (fakeClient .List (ctx , secrets , client .InNamespace (cluster .Namespace ), client.MatchingLabels {"testing" : "yes" })).To (Succeed ())
865+ for _ , secret := range secrets .Items {
866+ g .Expect (secret .OwnerReferences ).To (HaveLen (1 ))
867+ g .Expect (secret .OwnerReferences ).To (ContainElement (* metav1 .NewControllerRef (kcp , controlplanev1 .GroupVersion .WithKind ("KubeadmControlPlane" ))))
868+ }
869+ })
870+
871+ t .Run ("does not add owner reference to user-provided secrets" , func (t * testing.T ) {
872+ g := NewWithT (t )
873+ objs := []client.Object {fakeGenericMachineTemplateCRD , cluster .DeepCopy (), kcp .DeepCopy (), tmpl .DeepCopy ()}
874+ for _ , purpose := range []secret.Purpose {secret .ClusterCA , secret .FrontProxyCA , secret .ServiceAccount , secret .EtcdCA } {
875+ s := clusterSecret .DeepCopy ()
876+ // Set the secret name to the purpose
877+ s .Name = secret .Name (cluster .Name , purpose )
878+ // Set the Secret Type to any type which signals this Secret was is user-provided.
879+ s .Type = corev1 .SecretTypeOpaque
880+ // Set the a controller owner reference of an unknown type on the secret.
881+ s .SetOwnerReferences ([]metav1.OwnerReference {
882+ {
883+ APIVersion : bootstrapv1 .GroupVersion .String (),
884+ // This owner reference to a different controller should be preserved.
885+ Kind : "OtherController" ,
886+ Name : kcp .Name ,
887+ UID : kcp .UID ,
888+ Controller : pointer .Bool (true ),
889+ BlockOwnerDeletion : pointer .Bool (true ),
890+ },
891+ })
892+
893+ objs = append (objs , s )
894+ }
895+
896+ fakeClient := newFakeClient (objs ... )
897+ fmc .Reader = fakeClient
898+ r := & KubeadmControlPlaneReconciler {
899+ Client : fakeClient ,
900+ APIReader : fakeClient ,
901+ managementCluster : fmc ,
902+ managementClusterUncached : fmc ,
903+ }
904+
905+ _ , err := r .reconcile (ctx , cluster , kcp )
906+ g .Expect (err ).To (BeNil ())
907+
908+ secrets := & corev1.SecretList {}
909+ g .Expect (fakeClient .List (ctx , secrets , client .InNamespace (cluster .Namespace ), client.MatchingLabels {"testing" : "yes" })).To (Succeed ())
910+ for _ , secret := range secrets .Items {
911+ g .Expect (secret .OwnerReferences ).To (HaveLen (1 ))
912+ g .Expect (secret .OwnerReferences ).To (ContainElement (* metav1 .NewControllerRef (kcp , bootstrapv1 .GroupVersion .WithKind ("OtherController" ))))
913+ }
914+ })
915+ }
916+
752917func TestReconcileCertificateExpiries (t * testing.T ) {
753918 g := NewWithT (t )
754919
@@ -1769,3 +1934,34 @@ func newCluster(namespacedName *types.NamespacedName) *clusterv1.Cluster {
17691934 },
17701935 }
17711936}
1937+
1938+ func getTestCACert (key * rsa.PrivateKey ) (* x509.Certificate , error ) {
1939+ cfg := certs.Config {
1940+ CommonName : "kubernetes" ,
1941+ }
1942+
1943+ now := time .Now ().UTC ()
1944+
1945+ tmpl := x509.Certificate {
1946+ SerialNumber : new (big.Int ).SetInt64 (0 ),
1947+ Subject : pkix.Name {
1948+ CommonName : cfg .CommonName ,
1949+ Organization : cfg .Organization ,
1950+ },
1951+ NotBefore : now .Add (time .Minute * - 5 ),
1952+ NotAfter : now .Add (time .Hour * 24 ), // 1 day
1953+ KeyUsage : x509 .KeyUsageKeyEncipherment | x509 .KeyUsageDigitalSignature | x509 .KeyUsageCertSign ,
1954+ MaxPathLenZero : true ,
1955+ BasicConstraintsValid : true ,
1956+ MaxPathLen : 0 ,
1957+ IsCA : true ,
1958+ }
1959+
1960+ b , err := x509 .CreateCertificate (rand .Reader , & tmpl , & tmpl , key .Public (), key )
1961+ if err != nil {
1962+ return nil , err
1963+ }
1964+
1965+ c , err := x509 .ParseCertificate (b )
1966+ return c , err
1967+ }
0 commit comments