diff --git a/cmd/cluster/checkexpiration/checkexpiration.go b/cmd/cluster/checkexpiration/checkexpiration.go deleted file mode 100644 index bf9477702..000000000 --- a/cmd/cluster/checkexpiration/checkexpiration.go +++ /dev/null @@ -1,86 +0,0 @@ -/* - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package checkexpiration - -import ( - "github.com/spf13/cobra" - - "opendev.org/airship/airshipctl/pkg/cluster/checkexpiration" - "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/k8s/client" - "opendev.org/airship/airshipctl/pkg/log" -) - -const ( - checkLong = ` -Displays a list of certificate along with expirations from both the management and workload clusters, or in a -self-managed cluster. Checks for TLS Secrets, kubeconf secrets (which gets created while creating the -workload cluster) and also the node certificates present inside /etc/kubernetes/pki directory for each node. -` - - checkExample = ` -To display all the expiring certificates in the cluster -# airshipctl cluster check-certificate-expiration --kubeconfig testconfig - -To display the certificates whose expiration is within threshold of 30 days -# airshipctl cluster check-certificate-expiration -t 30 --kubeconfig testconfig - -To output the contents in json format (default operation) -# airshipctl cluster check-certificate-expiration -o json --kubeconfig testconfig -or -# airshipctl cluster check-certificate-expiration --kubeconfig testconfig - -To output the contents in yaml format -# airshipctl cluster check-certificate-expiration -o yaml --kubeconfig testconfig - -To output the contents whose expiration is within 30 days in yaml format -# airshipctl cluster check-certificate-expiration -t 30 -o yaml --kubeconfig testconfig -` - - kubeconfigFlag = "kubeconfig" -) - -// NewCheckCommand creates a new command for generating secret information -func NewCheckCommand(cfgFactory config.Factory) *cobra.Command { - c := &checkexpiration.CheckCommand{ - Options: checkexpiration.CheckFlags{}, - CfgFactory: cfgFactory, - ClientFactory: client.DefaultClient, - } - - checkCmd := &cobra.Command{ - Use: "check-certificate-expiration", - Short: "Airshipctl command to check expiring TLS certificates, " + - "secrets and kubeconfigs in the kubernetes cluster", - Long: checkLong[1:], - Example: checkExample, - RunE: func(cmd *cobra.Command, args []string) error { - return c.RunE(cmd.OutOrStdout()) - }, - } - - checkCmd.Flags().StringVarP(&c.Options.FormatType, "output", "o", "json", "convert output to yaml or json") - checkCmd.Flags().StringVar(&c.Options.KubeContext, "kubecontext", "", "kubeconfig context to be used") - checkCmd.Flags().StringVar(&c.Options.Kubeconfig, kubeconfigFlag, "", - "path to kubeconfig associated with cluster being managed") - checkCmd.Flags().IntVarP(&c.Options.Threshold, "threshold", "t", -1, - "the max expiration threshold in days before a certificate is expiring. Displays all the certificates by default") - - err := checkCmd.MarkFlagRequired(kubeconfigFlag) - if err != nil { - log.Fatalf("marking kubeconfig flag required failed: %v", err) - } - return checkCmd -} diff --git a/cmd/cluster/checkexpiration/checkexpiration_test.go b/cmd/cluster/checkexpiration/checkexpiration_test.go deleted file mode 100644 index 0f53c1575..000000000 --- a/cmd/cluster/checkexpiration/checkexpiration_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package checkexpiration_test - -import ( - "testing" - - "opendev.org/airship/airshipctl/cmd/cluster/checkexpiration" - "opendev.org/airship/airshipctl/testutil" -) - -func TestCheckExpiration(t *testing.T) { - cmdTests := []*testutil.CmdTest{ - { - Name: "check-expiration-with-help", - CmdLine: "--help", - Cmd: checkexpiration.NewCheckCommand(nil), - }, - } - - for _, tt := range cmdTests { - testutil.RunTest(t, tt) - } -} diff --git a/cmd/cluster/checkexpiration/testdata/TestCheckExpirationGoldenOutput/check-expiration-with-help.golden b/cmd/cluster/checkexpiration/testdata/TestCheckExpirationGoldenOutput/check-expiration-with-help.golden deleted file mode 100644 index d10b18305..000000000 --- a/cmd/cluster/checkexpiration/testdata/TestCheckExpirationGoldenOutput/check-expiration-with-help.golden +++ /dev/null @@ -1,33 +0,0 @@ -Displays a list of certificate along with expirations from both the management and workload clusters, or in a -self-managed cluster. Checks for TLS Secrets, kubeconf secrets (which gets created while creating the -workload cluster) and also the node certificates present inside /etc/kubernetes/pki directory for each node. - -Usage: - check-certificate-expiration [flags] - -Examples: - -To display all the expiring certificates in the cluster -# airshipctl cluster check-certificate-expiration --kubeconfig testconfig - -To display the certificates whose expiration is within threshold of 30 days -# airshipctl cluster check-certificate-expiration -t 30 --kubeconfig testconfig - -To output the contents in json format (default operation) -# airshipctl cluster check-certificate-expiration -o json --kubeconfig testconfig -or -# airshipctl cluster check-certificate-expiration --kubeconfig testconfig - -To output the contents in yaml format -# airshipctl cluster check-certificate-expiration -o yaml --kubeconfig testconfig - -To output the contents whose expiration is within 30 days in yaml format -# airshipctl cluster check-certificate-expiration -t 30 -o yaml --kubeconfig testconfig - - -Flags: - -h, --help help for check-certificate-expiration - --kubeconfig string path to kubeconfig associated with cluster being managed - --kubecontext string kubeconfig context to be used - -o, --output string convert output to yaml or json (default "json") - -t, --threshold int the max expiration threshold in days before a certificate is expiring. Displays all the certificates by default (default -1) diff --git a/cmd/cluster/cluster.go b/cmd/cluster/cluster.go index b53426f4c..e5db9ca0e 100644 --- a/cmd/cluster/cluster.go +++ b/cmd/cluster/cluster.go @@ -17,7 +17,6 @@ package cluster import ( "github.com/spf13/cobra" - "opendev.org/airship/airshipctl/cmd/cluster/checkexpiration" "opendev.org/airship/airshipctl/cmd/cluster/resetsatoken" "opendev.org/airship/airshipctl/pkg/config" ) @@ -40,7 +39,6 @@ func NewClusterCommand(cfgFactory config.Factory) *cobra.Command { clusterRootCmd.AddCommand(NewStatusCommand(cfgFactory)) clusterRootCmd.AddCommand(resetsatoken.NewResetCommand(cfgFactory)) - clusterRootCmd.AddCommand(checkexpiration.NewCheckCommand(cfgFactory)) clusterRootCmd.AddCommand(NewGetKubeconfigCommand(cfgFactory)) clusterRootCmd.AddCommand(NewListCommand(cfgFactory)) diff --git a/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden b/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden index bc29129a1..11ab4760e 100644 --- a/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden +++ b/cmd/cluster/testdata/TestNewClusterCommandGoldenOutput/cluster-cmd-with-help.golden @@ -5,12 +5,11 @@ Usage: cluster [command] Available Commands: - check-certificate-expiration Airshipctl command to check expiring TLS certificates, secrets and kubeconfigs in the kubernetes cluster - get-kubeconfig Airshipctl command to retrieve kubeconfig for a desired cluster - help Help about any command - list Airshipctl command to get and list defined clusters - rotate-sa-token Airshipctl command to rotate tokens of Service Account(s) - status Retrieve statuses of deployed cluster components + get-kubeconfig Airshipctl command to retrieve kubeconfig for a desired cluster + help Help about any command + list Airshipctl command to get and list defined clusters + rotate-sa-token Airshipctl command to rotate tokens of Service Account(s) + status Retrieve statuses of deployed cluster components Flags: -h, --help help for cluster diff --git a/docs/source/cli/cluster/airshipctl_cluster.rst b/docs/source/cli/cluster/airshipctl_cluster.rst index 020136e6d..71947307c 100644 --- a/docs/source/cli/cluster/airshipctl_cluster.rst +++ b/docs/source/cli/cluster/airshipctl_cluster.rst @@ -32,7 +32,6 @@ SEE ALSO ~~~~~~~~ * :ref:`airshipctl ` - A unified command line tool for management of end-to-end kubernetes cluster deployment on cloud infrastructure environments. -* :ref:`airshipctl cluster check-certificate-expiration ` - Airshipctl command to check expiring TLS certificates, secrets and kubeconfigs in the kubernetes cluster * :ref:`airshipctl cluster get-kubeconfig ` - Airshipctl command to retrieve kubeconfig for a desired cluster * :ref:`airshipctl cluster list ` - Airshipctl command to get and list defined clusters * :ref:`airshipctl cluster rotate-sa-token ` - Airshipctl command to rotate tokens of Service Account(s) diff --git a/docs/source/cli/cluster/index.rst b/docs/source/cli/cluster/index.rst index bb83cbee7..a6b5347de 100644 --- a/docs/source/cli/cluster/index.rst +++ b/docs/source/cli/cluster/index.rst @@ -6,7 +6,6 @@ cluster :maxdepth: 2 airshipctl_cluster - airshipctl_cluster_check-certificate-expiration airshipctl_cluster_get-kubeconfig airshipctl_cluster_list airshipctl_cluster_rotate-sa-token diff --git a/pkg/cluster/checkexpiration/checkexpiration.go b/pkg/cluster/checkexpiration/checkexpiration.go deleted file mode 100644 index 58cc2887d..000000000 --- a/pkg/cluster/checkexpiration/checkexpiration.go +++ /dev/null @@ -1,350 +0,0 @@ -/* - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package checkexpiration - -import ( - "crypto/x509" - "encoding/pem" - "fmt" - "log" - "strings" - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - - "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/k8s/client" -) - -const ( - kubeconfigIdentifierSuffix = "-kubeconfig" - timeFormat = "Jan 02, 2006 15:04 MST" - nodeCertExpirationAnnotation = "cert-expiration" -) - -// CertificateExpirationStore is the customized client store -type CertificateExpirationStore struct { - Kclient client.Interface - Settings config.Factory - ExpirationThreshold int -} - -// NewStore returns an instance of a CertificateExpirationStore -func NewStore(cfgFactory config.Factory, clientFactory client.Factory, - kubeconfig, _ string, expirationThreshold int) (CertificateExpirationStore, error) { - airshipconfig, err := cfgFactory() - if err != nil { - return CertificateExpirationStore{}, err - } - - // TODO (guhan) Allow kube context to be passed to client Factory - // 4th argument in NewStore takes kube context and is ignored for now. - // To be modified post #388. Refer to - // https://review.opendev.org/#/c/760501/7/pkg/cluster/checkexpiration/command.go@31 - kclient, err := clientFactory(airshipconfig.LoadedConfigPath(), kubeconfig) - if err != nil { - return CertificateExpirationStore{}, err - } - - return CertificateExpirationStore{ - Kclient: kclient, - Settings: cfgFactory, - ExpirationThreshold: expirationThreshold, - }, nil -} - -// GetExpiringTLSCertificates returns the list of TLS certificates whose expiration date -// falls within the given expirationThreshold -func (store CertificateExpirationStore) GetExpiringTLSCertificates() ([]TLSSecret, error) { - secrets, err := store.getAllTLSCertificates() - if err != nil { - return nil, err - } - - tlsData := make([]TLSSecret, 0) - for _, secret := range secrets.Items { - expiringCertificates := store.getExpiringCertificates(secret) - if len(expiringCertificates) > 0 { - tlsData = append(tlsData, TLSSecret{ - Name: secret.Name, - Namespace: secret.Namespace, - ExpiringCertificates: expiringCertificates, - }) - } - } - return tlsData, nil -} - -// getAllTLSCertificates juist returns all the k8s secrets with tyoe as TLS -func (store CertificateExpirationStore) getAllTLSCertificates() (*corev1.SecretList, error) { - secretTypeFieldSelector := fmt.Sprintf("type=%s", corev1.SecretTypeTLS) - listOptions := metav1.ListOptions{FieldSelector: secretTypeFieldSelector} - return store.getSecrets(listOptions) -} - -// getSecrets returns the secret list based on the listOptions -func (store CertificateExpirationStore) getSecrets(listOptions metav1.ListOptions) (*corev1.SecretList, error) { - return store.Kclient.ClientSet().CoreV1().Secrets("").List(listOptions) -} - -// getExpiringCertificates skims through all the TLS certificates and returns the ones -// lesser than threshold -func (store CertificateExpirationStore) getExpiringCertificates(secret corev1.Secret) map[string]string { - expiringCertificates := map[string]string{} - for _, certName := range []string{corev1.TLSCertKey, corev1.ServiceAccountRootCAKey} { - if cert, found := secret.Data[certName]; found { - expirationDate, err := extractExpirationDateFromCertificate(cert) - if err != nil { - log.Printf("Unable to parse certificate for %s in secret %s in namespace %s: %v", - certName, secret.Name, secret.Namespace, err) - continue - } - - if isWithinDuration(expirationDate, store.ExpirationThreshold) { - expiringCertificates[certName] = expirationDate.String() - } - } - } - return expiringCertificates -} - -// isWithinDuration checks if the certificate expirationDate is within the duration (input) -func isWithinDuration(expirationDate time.Time, duration int) bool { - if duration < 0 { - return true - } - daysUntilExpiration := int(time.Until(expirationDate).Hours() / 24) - return 0 <= daysUntilExpiration && daysUntilExpiration < duration -} - -// extractExpirationDateFromCertificate parses the certificate and returns the expiration date -func extractExpirationDateFromCertificate(certData []byte) (time.Time, error) { - block, _ := pem.Decode(certData) - if block == nil { - return time.Time{}, ErrPEMFail{Context: "decode", Err: "no PEM data could be found"} - } - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return time.Time{}, ErrPEMFail{Context: "parse", Err: err.Error()} - } - return cert.NotAfter, nil -} - -// GetExpiringKubeConfigs - fetches all the '-kubeconfig' secrets and identifies expiration -func (store CertificateExpirationStore) GetExpiringKubeConfigs() ([]Kubeconfig, error) { - kubeconfigs, err := store.getKubeconfSecrets() - - if err != nil { - return nil, err - } - - kSecretData := make([]Kubeconfig, 0) - - for _, kubeconfig := range kubeconfigs { - kubecontent, err := clientcmd.Load(kubeconfig.Data["value"]) - if err != nil { - log.Printf("Failed to read kubeconfig from %s in %s-"+ - "it maybe malformed : %v", kubeconfig.Name, kubeconfig.Namespace, err.Error()) - continue - } - - expiringClusters := store.getExpiringClusterCertificates(kubecontent) - - expiringUsers := store.getExpiringUserCertificates(kubecontent) - - if len(expiringClusters) > 0 || len(expiringUsers) > 0 { - kSecretData = append(kSecretData, Kubeconfig{ - SecretName: kubeconfig.Name, - SecretNamespace: kubeconfig.Namespace, - Cluster: expiringClusters, - User: expiringUsers, - }) - } - } - return kSecretData, nil -} - -// filterKubeConfigs identifies the kubeconfig secrets based on the kubeconfigIdentifierSuffix -func filterKubeConfigs(secrets []corev1.Secret) []corev1.Secret { - filteredSecrets := []corev1.Secret{} - for _, secret := range secrets { - if strings.HasSuffix(secret.Name, kubeconfigIdentifierSuffix) { - filteredSecrets = append(filteredSecrets, secret) - } - } - return filteredSecrets -} - -// filterOwners allows only the secrets with Ownerreferences matching ownerKind -func filterOwners(secrets []corev1.Secret, ownerKind string) []corev1.Secret { - filteredSecrets := []corev1.Secret{} - for _, secret := range secrets { - for _, ownerRef := range secret.OwnerReferences { - if ownerRef.Kind == ownerKind { - filteredSecrets = append(filteredSecrets, secret) - } - } - } - return filteredSecrets -} - -func (store CertificateExpirationStore) getExpiringClusterCertificates( - kubeconfig *clientcmdapi.Config) []kubeconfData { - expiringClusterCertificates := make([]kubeconfData, 0) - - // Iterate through each Cluster and identify expiration - for clusterName, clusterData := range kubeconfig.Clusters { - expirationDate, err := extractExpirationDateFromCertificate(clusterData.CertificateAuthorityData) - if err != nil { - log.Printf("Unable to parse certificate for %s : %v", clusterName, err) - continue - } - - if isWithinDuration(expirationDate, store.ExpirationThreshold) { - expiringClusterCertificates = append(expiringClusterCertificates, kubeconfData{ - Name: clusterName, - CertificateName: "CertificateAuthorityData", - ExpirationDate: expirationDate.String(), - }) - } - } - return expiringClusterCertificates -} - -func (store CertificateExpirationStore) getExpiringUserCertificates( - kubeconfig *clientcmdapi.Config) []kubeconfData { - expiringUserCertificates := make([]kubeconfData, 0) - - // Iterate through each User and identify expiration - for userName, userData := range kubeconfig.AuthInfos { - expirationDate, err := extractExpirationDateFromCertificate(userData.ClientCertificateData) - if err != nil { - log.Printf("Unable to parse certificate for %s : %v", userName, err) - continue - } - - if isWithinDuration(expirationDate, store.ExpirationThreshold) { - expiringUserCertificates = append(expiringUserCertificates, kubeconfData{ - Name: userName, - CertificateName: "ClientCertificateData", - ExpirationDate: expirationDate.String(), - }) - } - } - return expiringUserCertificates -} - -// getKubeconfSecrets filters the kubeconf secrets -func (store CertificateExpirationStore) getKubeconfSecrets() ([]corev1.Secret, error) { - secrets, err := store.getSecrets(metav1.ListOptions{}) - if err != nil { - return nil, err - } - - kubeconfigs := filterKubeConfigs(secrets.Items) - kubeconfigs = filterOwners(kubeconfigs, "KubeadmControlPlane") - return kubeconfigs, nil -} - -// GetExpiringNodeCertificates runs through all the nodes and identifies expiration -func (store CertificateExpirationStore) GetExpiringNodeCertificates() ([]NodeCert, error) { - // Node will be updated with an annotation with the expiry content (Activity - // of HostConfig Operator - 'check-expiry' CR Object) every day (Cron like - // activity is performed by reconcile tag in the Operator) Below code is - // implemented to just read the annotation, parse it, identify expirable - // content and report back - - // Expected Annotation Format: - // "cert-expiration": "{ admin.conf: Aug 06, 2021 12:36 UTC }, - // { apiserver: Aug 06, 2021 12:36 UTC }, - // { apiserver-etcd-client: Aug 06, 2021 12:36 UTC }, - // { apiserver-kubelet-client: Aug 06, 2021 12:36 UTC }, - // { controller-manager.conf: Aug 06, 2021 12:36 UTC }, - // { etcd-healthcheck-client: Aug 06, 2021 12:36 UTC }, - // { etcd-peer: Aug 06, 2021 12:36 UTC }, - // { etcd-server: Aug 06, 2021 12:36 UTC }, - // { front-proxy-client: Aug 06, 2021 12:36 UTC }, - // { scheduler.conf: Aug 06, 2021 12:36 UTC }, - // { ca: Aug 04, 2030 12:36 UTC }, - // { etcd-ca: Aug 04, 2030 12:36 UTC }, - // { front-proxy-ca: Aug 04, 2030 12:36 UTC }" - - nodes, err := store.getNodes(metav1.ListOptions{}) - if err != nil { - return nil, err - } - - nodeData := make([]NodeCert, 0) - for _, node := range nodes.Items { - expiringNodeCertificates := store.getExpiringNodeCertificates(node) - if len(expiringNodeCertificates) > 0 { - nodeData = append(nodeData, NodeCert{ - Name: node.Name, - Namespace: node.Namespace, - ExpiringCertificates: expiringNodeCertificates, - }) - } - } - return nodeData, nil -} - -// getSecrets returns the Nodes list based on the listOptions -func (store CertificateExpirationStore) getNodes(listOptions metav1.ListOptions) (*corev1.NodeList, error) { - return store.Kclient.ClientSet().CoreV1().Nodes().List(listOptions) -} - -// getExpiringNodeCertificates skims through all the node certificates and returns -// the ones lesser than threshold -func (store CertificateExpirationStore) getExpiringNodeCertificates(node corev1.Node) map[string]string { - if cert, found := node.ObjectMeta.Annotations[nodeCertExpirationAnnotation]; found { - certificateList := splitAsList(cert) - - expiringCertificates := map[string]string{} - for _, certificate := range certificateList { - certificateName, expirationDate := identifyCertificateNameAndExpirationDate(certificate) - if certificateName != "" && isWithinDuration(expirationDate, store.ExpirationThreshold) { - expiringCertificates[certificateName] = expirationDate.String() - } - } - return expiringCertificates - } - log.Printf("%s annotation missing for node %s in %s", nodeCertExpirationAnnotation, - node.Name, node.Namespace) - return nil -} - -// splitAsList performes the required string manipulations and returns list of items -func splitAsList(value string) []string { - return strings.Split(strings.ReplaceAll(value, "{", ""), "},") -} - -// identifyCertificateNameAndExpirationDate performs string manipulations and returns -// certificate name and its expiration date -func identifyCertificateNameAndExpirationDate(certificate string) (string, time.Time) { - certificateName := strings.TrimSpace(strings.Split(certificate, ":")[0]) - expirationDate := strings.TrimSpace(strings.Split(certificate, ":")[1]) + - ":" + - strings.TrimSpace(strings.ReplaceAll(strings.Split(certificate, ":")[2], "}", "")) - - formattedExpirationDate, err := time.Parse(timeFormat, expirationDate) - if err != nil { - log.Printf(err.Error()) - return "", time.Time{} - } - return certificateName, formattedExpirationDate -} diff --git a/pkg/cluster/checkexpiration/checkexpiration_test.go b/pkg/cluster/checkexpiration/checkexpiration_test.go deleted file mode 100644 index 9dd1d2b64..000000000 --- a/pkg/cluster/checkexpiration/checkexpiration_test.go +++ /dev/null @@ -1,128 +0,0 @@ -/* - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package checkexpiration_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "k8s.io/apimachinery/pkg/runtime" - - "opendev.org/airship/airshipctl/pkg/cluster/checkexpiration" - "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/k8s/client" - "opendev.org/airship/airshipctl/pkg/k8s/client/fake" - "opendev.org/airship/airshipctl/testutil" -) - -type testCase struct { - name string - expiryThreshold int - nodeTestFile string - kubeconfTestFile string - tlsSecretTestFile string - nodeExpirationYear string - expectedExpiringNodeCount int - expectedExpiringKubeConfigCount int - expectedExpiringTLSSecretCount int -} - -var ( - testCases = []*testCase{ - { - name: "empty-expect-error", - expectedExpiringNodeCount: 0, - expectedExpiringKubeConfigCount: 0, - expectedExpiringTLSSecretCount: 0, - }, - { - name: "node-cert-expiring", - nodeTestFile: nodeFile, - nodeExpirationYear: "2021", - expiryThreshold: testThreshold, // 20 years - expectedExpiringNodeCount: 1, - }, - { - name: "node-cert-not-expiring", - nodeExpirationYear: "2025", - nodeTestFile: nodeFile, - expiryThreshold: 10, - expectedExpiringNodeCount: 0, - }, - { - name: "all-certs-not-expiring", - nodeExpirationYear: "2025", - nodeTestFile: nodeFile, - tlsSecretTestFile: tlsSecretFile, - kubeconfTestFile: kubeconfFile, - expiryThreshold: 1, - expectedExpiringNodeCount: 0, - expectedExpiringKubeConfigCount: 0, - expectedExpiringTLSSecretCount: 0, - }, - { - name: "all-certs-expiring", - nodeExpirationYear: "2021", - nodeTestFile: nodeFile, - tlsSecretTestFile: tlsSecretFile, - kubeconfTestFile: kubeconfFile, - expiryThreshold: testThreshold, - expectedExpiringNodeCount: 1, - expectedExpiringKubeConfigCount: 1, - expectedExpiringTLSSecretCount: 1, - }, - } -) - -func TestCheckExpiration(t *testing.T) { - for _, testCase := range testCases { - cfg, _ := testutil.InitConfig(t) - settings := func() (*config.Config, error) { - return cfg, nil - } - - var objects []runtime.Object - - if testCase.nodeExpirationYear != "" && testCase.nodeTestFile != "" { - objects = append(objects, getNodeObject(t, testCase.nodeTestFile, testCase.nodeExpirationYear)) - } - - if testCase.tlsSecretTestFile != "" { - objects = append(objects, getSecretObject(t, testCase.tlsSecretTestFile)) - } - - if testCase.kubeconfTestFile != "" { - objects = append(objects, getSecretObject(t, testCase.kubeconfTestFile)) - } - - ra := fake.WithTypedObjects(objects...) - - clientFactory := func(_ string, _ string) (client.Interface, error) { - return fake.NewClient(ra), nil - } - - store, err := checkexpiration.NewStore(settings, clientFactory, "", "", testCase.expiryThreshold) - assert.NoError(t, err) - - expirationInfo := store.GetExpiringCertificates() - - assert.Len(t, expirationInfo.Kubeconfs, testCase.expectedExpiringKubeConfigCount) - - assert.Len(t, expirationInfo.TLSSecrets, testCase.expectedExpiringTLSSecretCount) - - assert.Len(t, expirationInfo.NodeCerts, testCase.expectedExpiringNodeCount) - } -} diff --git a/pkg/cluster/checkexpiration/command.go b/pkg/cluster/checkexpiration/command.go deleted file mode 100644 index 0765b237a..000000000 --- a/pkg/cluster/checkexpiration/command.go +++ /dev/null @@ -1,133 +0,0 @@ -/* - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package checkexpiration - -import ( - "encoding/json" - "io" - "strings" - - "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/k8s/client" - "opendev.org/airship/airshipctl/pkg/log" - "opendev.org/airship/airshipctl/pkg/util/yaml" -) - -// CheckFlags flags given for checking the expiration -type CheckFlags struct { - Threshold int - FormatType string - Kubeconfig string - KubeContext string -} - -// CheckCommand check expiration command -type CheckCommand struct { - Options CheckFlags - CfgFactory config.Factory - ClientFactory client.Factory -} - -// ExpirationStore captures expiration information of all expirable entities in the cluster -type ExpirationStore struct { - TLSSecrets []TLSSecret `json:"tlsSecrets,omitempty" yaml:"tlsSecrets,omitempty"` - Kubeconfs []Kubeconfig `json:"kubeconfs,omitempty" yaml:"kubeconfs,omitempty"` - NodeCerts []NodeCert `json:"nodeCerts,omitempty" yaml:"nodeCerts,omitempty"` -} - -// TLSSecret captures expiration information of certificates embedded in TLS secrets -type TLSSecret struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` - ExpiringCertificates map[string]string `json:"certificate,omitempty" yaml:"certificate,omitempty"` -} - -// Kubeconfig captures expiration information of all kubeconfigs -type Kubeconfig struct { - SecretName string `json:"secretName,omitempty" yaml:"secretName,omitempty"` - SecretNamespace string `json:"secretNamespace,omitempty" yaml:"secretNamespace,omitempty"` - Cluster []kubeconfData `json:"cluster,omitempty" yaml:"cluster,omitempty"` - User []kubeconfData `json:"user,omitempty" yaml:"user,omitempty"` -} - -// kubeconfData captures cluster ca certificate expiration information and kubeconfig's user's certificate -type kubeconfData struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - CertificateName string `json:"certificateName,omitempty" yaml:"certificateName,omitempty"` - ExpirationDate string `json:"expirationDate,omitempty" yaml:"expirationDate,omitempty"` -} - -// NodeCert captures certificate expiry information for certificates on each node -type NodeCert struct { - Name string `json:"name,omitempty" yaml:"name,omitempty"` - Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` - ExpiringCertificates map[string]string `json:"certificate,omitempty" yaml:"certificate,omitempty"` -} - -// RunE is the implementation of check command -func (c *CheckCommand) RunE(w io.Writer) error { - if !strings.EqualFold(c.Options.FormatType, "json") && !strings.EqualFold(c.Options.FormatType, "yaml") { - return ErrInvalidFormat{RequestedFormat: c.Options.FormatType} - } - - secretStore, err := NewStore(c.CfgFactory, c.ClientFactory, c.Options.Kubeconfig, - c.Options.KubeContext, c.Options.Threshold) - if err != nil { - return err - } - - expirationInfo := secretStore.GetExpiringCertificates() - - if c.Options.FormatType == "yaml" { - err = yaml.WriteOut(w, expirationInfo) - if err != nil { - return err - } - } else { - buffer, err := json.MarshalIndent(expirationInfo, "", " ") - if err != nil { - return err - } - _, err = w.Write(buffer) - if err != nil { - return err - } - } - return nil -} - -// GetExpiringCertificates encapsulates all the different expirable entities in the cluster -func (store CertificateExpirationStore) GetExpiringCertificates() ExpirationStore { - expiringTLSCertificates, err := store.GetExpiringTLSCertificates() - if err != nil { - log.Printf(err.Error()) - } - - expiringKubeConfCertificates, err := store.GetExpiringKubeConfigs() - if err != nil { - log.Printf(err.Error()) - } - - expiringNodeCertificates, err := store.GetExpiringNodeCertificates() - if err != nil { - log.Printf(err.Error()) - } - - return ExpirationStore{ - TLSSecrets: expiringTLSCertificates, - Kubeconfs: expiringKubeConfCertificates, - NodeCerts: expiringNodeCertificates, - } -} diff --git a/pkg/cluster/checkexpiration/command_test.go b/pkg/cluster/checkexpiration/command_test.go deleted file mode 100644 index 1748cbe58..000000000 --- a/pkg/cluster/checkexpiration/command_test.go +++ /dev/null @@ -1,257 +0,0 @@ -/* - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package checkexpiration_test - -import ( - "bytes" - "io/ioutil" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/yaml" - "k8s.io/kubectl/pkg/scheme" - - "opendev.org/airship/airshipctl/pkg/cluster/checkexpiration" - "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/k8s/client" - "opendev.org/airship/airshipctl/pkg/k8s/client/fake" - "opendev.org/airship/airshipctl/testutil" -) - -const ( - testThreshold = 7200 - - nodeFile = "testdata/node.yaml" - kubeconfFile = "testdata/kubeconfig.yaml" - tlsSecretFile = "testdata/tls-secret.yaml" //nolint:gosec - - expectedJSONOutput = ` { - "tlsSecrets": [ - { - "name": "test-cluster-etcd", - "namespace": "target-infra", - "certificate": { - "ca.crt": "2030-08-31 10:12:49 +0000 UTC", - "tls.crt": "2030-08-31 10:12:49 +0000 UTC" - } - } - ], - "kubeconfs": [ - { - "secretName": "test-cluster-kubeconfig", - "secretNamespace": "target-infra", - "cluster": [ - { - "name": "workload-cluster", - "certificateName": "CertificateAuthorityData", - "expirationDate": "2030-08-31 10:12:48 +0000 UTC" - } - ], - "user": [ - { - "name": "workload-cluster-admin", - "certificateName": "ClientCertificateData", - "expirationDate": "2021-09-02 10:12:50 +0000 UTC" - } - ] - } - ], - "nodeCerts": [ - { - "name": "test-node", - "certificate": { - "admin.conf": "2021-08-06 12:36:00 +0000 UTC", - "apiserver": "2021-08-06 12:36:00 +0000 UTC", - "apiserver-etcd-client": "2021-08-06 12:36:00 +0000 UTC", - "apiserver-kubelet-client": "2021-08-06 12:36:00 +0000 UTC", - "ca": "2021-08-04 12:36:00 +0000 UTC", - "controller-manager.conf": "2021-08-06 12:36:00 +0000 UTC", - "etcd-ca": "2021-08-04 12:36:00 +0000 UTC", - "etcd-healthcheck-client": "2021-08-06 12:36:00 +0000 UTC", - "etcd-peer": "2021-08-06 12:36:00 +0000 UTC", - "etcd-server": "2021-08-06 12:36:00 +0000 UTC", - "front-proxy-ca": "2021-08-04 12:36:00 +0000 UTC", - "front-proxy-client": "2021-08-06 12:36:00 +0000 UTC", - "scheduler.conf": "2021-08-06 12:36:00 +0000 UTC" - } - } - ] - }` - - expectedYAMLOutput = ` ---- -kubeconfs: -- cluster: - - certificateName: CertificateAuthorityData - expirationDate: 2030-08-31 10:12:48 +0000 UTC - name: workload-cluster - secretName: test-cluster-kubeconfig - secretNamespace: target-infra - user: - - certificateName: ClientCertificateData - expirationDate: 2021-09-02 10:12:50 +0000 UTC - name: workload-cluster-admin -tlsSecrets: -- certificate: - ca.crt: 2030-08-31 10:12:49 +0000 UTC - tls.crt: 2030-08-31 10:12:49 +0000 UTC - name: test-cluster-etcd - namespace: target-infra -nodeCerts: -- name: test-node - certificate: - admin.conf: 2021-08-06 12:36:00 +0000 UTC - apiserver: 2021-08-06 12:36:00 +0000 UTC - apiserver-etcd-client: 2021-08-06 12:36:00 +0000 UTC - apiserver-kubelet-client: 2021-08-06 12:36:00 +0000 UTC - ca: 2021-08-04 12:36:00 +0000 UTC - controller-manager.conf: 2021-08-06 12:36:00 +0000 UTC - etcd-ca: 2021-08-04 12:36:00 +0000 UTC - etcd-healthcheck-client: 2021-08-06 12:36:00 +0000 UTC - etcd-peer: 2021-08-06 12:36:00 +0000 UTC - etcd-server: 2021-08-06 12:36:00 +0000 UTC - front-proxy-ca: 2021-08-04 12:36:00 +0000 UTC - front-proxy-client: 2021-08-06 12:36:00 +0000 UTC - scheduler.conf: 2021-08-06 12:36:00 +0000 UTC -... -` -) - -func TestRunE(t *testing.T) { - tests := []struct { - testCaseName string - testErr string - checkFlags checkexpiration.CheckFlags - cfgFactory config.Factory - expectedOutput string - }{ - { - testCaseName: "invalid-input-format", - cfgFactory: func() (*config.Config, error) { - return nil, nil - }, - checkFlags: checkexpiration.CheckFlags{ - Threshold: 0, - FormatType: "test-yaml", - }, - testErr: checkexpiration.ErrInvalidFormat{RequestedFormat: "test-yaml"}.Error(), - }, - { - testCaseName: "valid-input-format-json", - cfgFactory: func() (*config.Config, error) { - cfg, _ := testutil.InitConfig(t) - return cfg, nil - }, - checkFlags: checkexpiration.CheckFlags{ - Threshold: testThreshold, - FormatType: "json", - Kubeconfig: "", - }, - testErr: "", - expectedOutput: expectedJSONOutput, - }, - { - testCaseName: "valid-input-format-yaml", - cfgFactory: func() (*config.Config, error) { - cfg, _ := testutil.InitConfig(t) - return cfg, nil - }, - checkFlags: checkexpiration.CheckFlags{ - Threshold: testThreshold, - FormatType: "yaml", - }, - testErr: "", - expectedOutput: expectedYAMLOutput, - }, - } - - for _, tt := range tests { - t.Run(tt.testCaseName, func(t *testing.T) { - objects := []runtime.Object{ - getSecretObject(t, tlsSecretFile), - getSecretObject(t, kubeconfFile), - getNodeObject(t, nodeFile, "2021"), - } - ra := fake.WithTypedObjects(objects...) - - command := checkexpiration.CheckCommand{ - Options: tt.checkFlags, - CfgFactory: tt.cfgFactory, - ClientFactory: func(_ string, _ string) (client.Interface, error) { - return fake.NewClient(ra), nil - }, - } - - var buffer bytes.Buffer - err := command.RunE(&buffer) - - if tt.testErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.testErr) - } else { - require.NoError(t, err) - t.Log(buffer.String()) - switch tt.checkFlags.FormatType { - case "json": - assert.JSONEq(t, tt.expectedOutput, buffer.String()) - case "yaml": - assert.YAMLEq(t, tt.expectedOutput, buffer.String()) - } - } - }) - } -} - -func getSecretObject(t *testing.T, fileName string) *v1.Secret { - t.Helper() - - object := readObjectFromFile(t, fileName) - secret, ok := object.(*v1.Secret) - require.True(t, ok) - - return secret -} - -func getNodeObject(t *testing.T, fileName string, expirationYear string) *v1.Node { - t.Helper() - - object := readObjectFromFile(t, fileName) - node, ok := object.(*v1.Node) - require.True(t, ok) - - node.Annotations["cert-expiration"] = strings.ReplaceAll(node.Annotations["cert-expiration"], - "{{year}}", expirationYear) - - return node -} - -func readObjectFromFile(t *testing.T, fileName string) runtime.Object { - t.Helper() - - contents, err := ioutil.ReadFile(fileName) - require.NoError(t, err) - - jsonContents, err := yaml.ToJSON(contents) - require.NoError(t, err) - - object, err := runtime.Decode(scheme.Codecs.UniversalDeserializer(), jsonContents) - require.NoError(t, err) - - return object -} diff --git a/pkg/cluster/checkexpiration/errors.go b/pkg/cluster/checkexpiration/errors.go deleted file mode 100644 index 9258a258d..000000000 --- a/pkg/cluster/checkexpiration/errors.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package checkexpiration - -import "fmt" - -// ErrInvalidFormat is called when the user provides format other than yaml/json -type ErrInvalidFormat struct { - RequestedFormat string -} - -func (e ErrInvalidFormat) Error() string { - return fmt.Sprintf("invalid output format specified %s. Allowed values are json|yaml", e.RequestedFormat) -} - -// ErrPEMFail is called where there is a PEM related failure while parsing the certificate block -type ErrPEMFail struct { - Context string - Err string -} - -func (e ErrPEMFail) Error() string { - return fmt.Sprintf("failed to %s certificate PEM: %s", e.Context, e.Err) -} diff --git a/pkg/cluster/checkexpiration/testdata/kubeconfig.yaml b/pkg/cluster/checkexpiration/testdata/kubeconfig.yaml deleted file mode 100644 index 86b6dec88..000000000 --- a/pkg/cluster/checkexpiration/testdata/kubeconfig.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -data: - value: YXBpVmVyc2lvbjogdjEKY2x1c3RlcnM6Ci0gY2x1c3RlcjoKICAgIGNlcnRpZmljYXRlLWF1dGhvcml0eS1kYXRhOiBMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VONWVrTkRRV0pQWjBGM1NVSkJaMGxDUVVSQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSYzBaQlJFRldUVkpOZDBWUldVUldVVkZFUlhkd2NtUlhTbXdLWTIwMWJHUkhWbnBOUWpSWVJGUkpkMDFFYTNkTmFrVjNUVVJqTUU5R2IxaEVWRTEzVFVSbmVrMVVSWGROVkVrd1QwWnZkMFpVUlZSTlFrVkhRVEZWUlFwQmVFMUxZVE5XYVZwWVNuVmFXRkpzWTNwRFEwRlRTWGRFVVZsS1MyOWFTV2gyWTA1QlVVVkNRbEZCUkdkblJWQkJSRU5EUVZGdlEyZG5SVUpCVEVsWkNrb3lUbXRCWnpNeGVqaDRaRGQyUzBWWlIwWnFMMHc0Tm1SaFdEWTJkWGRWVEVRMVozRlJURlJDYVZkSFZqVk9NSEU0UjJwMGNFeGpObTVWVjIxU01Va0tUR0ZNVkdoWVNWTTRWRGhtZWxnNVVqaE5aV1pGZUhOMFVtSXZXbkZGZUVkVmRtVkxkaTlQVjBzNVlXOHlLM0F2T1ZBdmJXUmpVa1p2VG1aalF6Sm5OUXBrYW1GSGJsRlJZMXBPUTBOelRISTRMMWRuVkRSRlNGaERZMlU1UWpGc2NGWjVNU3RXVjNkRFZtWmhaRkpEUkdwWVlWVXZjMkZQT0ZGYU1sQXlWa0phQ2pobE1FSnpUazlaZEVWVWNHVnhZVk15V1ZkdlZuWlFNV3N2Y0dKMVVsTk9XRTFQUkRnNFZYTnNWMUl5UVdOVVZXbG1TWGR2ZUZweU1ISk5NMU55U0ZjS1lVOTFWbm9yWW1kTGQxZHdRVk4xWjJGV1ZrcHJVMkphTTJJd2Eyd3lOV1JxZEVGYU5rdHVhMHhWV21sMVIyeDNMMk5EUjBRdmRHOHdhbEZFTUZaSk1nb3ZhRGxvUlZGS2JFcEZRMmhzVjFkQ0swUk5RMEYzUlVGQllVMXRUVU5SZDBSbldVUldVakJRUVZGSUwwSkJVVVJCWjB0clRVSkpSMEV4VldSRmQwVkNDaTkzVVVsTlFWbENRV1k0UTBGUlFYZEVVVmxLUzI5YVNXaDJZMDVCVVVWTVFsRkJSR2RuUlVKQlNVZzBhSE55ZWpscE1UUTFkR2RQUW5CWldGRTNZakFLU0d0RU9ITkRZVmhTU0ZWU00zaDRRVkJpUVdScFJtSnpPRkJxUkN0VFNVcElTa2x1TW0xS05VcGxibGs1Y0ZwWmRrUlRXbTUyUldkT1UydDJkbWhKS3dwT2JtaGpUbU5sTHk5VlJXdHZOekIxWm5WRk1IaGhMMlJETWtKcVZFSktRakEyY0RKNFVYZ3hWREp2T1RKTVJIVTRhbkJqV1VwbFlWRkZjbTV0YVRWMUNrTTJTaXR1UkhwalpDdENZVk0wTm1ZeWREZG5RekZJTm1WYWJUVjRMMVJOWVVKU1VHNUNVWGxYU25wRU1YSjBjMEZyYTJkRFdVOTJlV2huUjJKSmNYY0tZa2N3U0RaME1WWnJZUzlRY0VGa2JuWXlWa2wwTkdGWmVtRkJTbTFPYXpWcU0xZFdWVU5OVURKaWRFYzFkMkYzWWtaRU1HazJVVUZtZUVVclNWcHVNZ3AwWXk5MFltTjJZelZPZVUxbFpUWTBZM2RhYlZkTlRIVkpWbVpHUlRGRGEyWk1TSEJuYlVSbmVWRXhhVzVtTDNsbmJGbHdUWFpEVWs5MGRIUjJPR3M5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzBLCiAgICBzZXJ2ZXI6IGh0dHBzOi8vMTcyLjE3LjAuMzo2NDQzCiAgbmFtZTogd29ya2xvYWQtY2x1c3Rlcgpjb250ZXh0czoKLSBjb250ZXh0OgogICAgY2x1c3Rlcjogd29ya2xvYWQtY2x1c3RlcgogICAgdXNlcjogd29ya2xvYWQtY2x1c3Rlci1hZG1pbgogIG5hbWU6IHdvcmtsb2FkLWNsdXN0ZXItYWRtaW5Ad29ya2xvYWQtY2x1c3RlcgpjdXJyZW50LWNvbnRleHQ6IHdvcmtsb2FkLWNsdXN0ZXItYWRtaW5Ad29ya2xvYWQtY2x1c3RlcgpraW5kOiBDb25maWcKcHJlZmVyZW5jZXM6IHt9CnVzZXJzOgotIG5hbWU6IHdvcmtsb2FkLWNsdXN0ZXItYWRtaW4KICB1c2VyOgogICAgY2xpZW50LWNlcnRpZmljYXRlLWRhdGE6IExTMHRMUzFDUlVkSlRpQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENrMUpTVU00YWtORFFXUnhaMEYzU1VKQlowbEpSRUpNVXpneVVVZHhWM2QzUkZGWlNrdHZXa2xvZG1OT1FWRkZURUpSUVhkR1ZFVlVUVUpGUjBFeFZVVUtRWGhOUzJFelZtbGFXRXAxV2xoU2JHTjZRV1ZHZHpCNVRVUkJOVTFFU1hoTlJFRXpUa1JvWVVaM01IbE5WRUUxVFVSSmVFMUVSWGxPVkVKaFRVUlJlQXBHZWtGV1FtZE9Wa0pCYjFSRWJrNDFZek5TYkdKVWNIUlpXRTR3V2xoS2VrMVNhM2RHZDFsRVZsRlJSRVY0UW5Ka1YwcHNZMjAxYkdSSFZucE1WMFpyQ21KWGJIVk5TVWxDU1dwQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSUlVaQlFVOURRVkU0UVUxSlNVSkRaMHREUVZGRlFYZEpURGxKZFROVWRHMXZZMHh5YlcwS1ZERlBkMk0yVFcxeFVVYzNlbFUzT0doVVFVaEpla3BOV0c5Tk1HRktSVkl2YTFoTWJraFBTazB2TkhCNEwwNVhWbWxHUjNWcFkybHJXSFUzZGxFd1VncHJOMnBZTWsxQlUwSXpaazB2S3pNNWVHVk1iakJOWkVsVllsaERTRnBSV1RaVmVrbFJhMFpoVlc4dldrOTNibk15VFhBeGRVdFNVVzlJUkhZNVZFazNDa0ZzVG10U1RYUk9SSHBOTXpGVFFXTXJVRWRGU0VaMGJ5OXRRVVpOUXpsUVdtaHRaMlpJY2twdWJsUkJWR3RZTW1ONlQxcFZaakl6ZUVSdk1XTkZjbklLZWk5aVRYTjBRMGhRWVdjMVRVcE9lWGczUmpCNFlVTlBOSFF2U2pZM2FXWkhORk5tTlVaMVkxVkxTemgxVGs1R1VGbFVSamhIWTI5R1duaGFRelZOTlFwdlEwUkdSVGRVUm5obWRXWTNaMWhpU0RSemVsQTBkMFZhTms5S1NIQTRjVUpHYlU4eVRrVndkR3N3T1VSbVpUSndUVFZxYzJSSWNXcE9jV2t2VVc5QkNrRTVURGhEVVVsRVFWRkJRbTk1WTNkS1ZFRlBRbWRPVmtoUk9FSkJaamhGUWtGTlEwSmhRWGRGZDFsRVZsSXdiRUpCZDNkRFoxbEpTM2RaUWtKUlZVZ0tRWGRKZDBSUldVcExiMXBKYUhaalRrRlJSVXhDVVVGRVoyZEZRa0ZDUXpZMlZHVkhaV3RtTlU1VU1sbHJSR0pEWjBsemR6TmpXbTV5UVRCYVdDdDZaQW95WXpRM2VGaFlibkJpUW1VeVJYQjNNSGwwUkZkNFpGWnVZa1JzVkVoaVUydERPRFl2ZEZSalpHNVFhMHB4YUM5MWEwSnpTbGxHV0hjclZVNDNlbXRDQ2pock5HY3pWbmRvWldWeU1raDZTVWxUZVV4MWJVZHJlRFJMVlhOblRGbzRjblZCWVU1VkswVlRjSFpyZWpOclpuY3dMMmhOUzB4MFNGTjRRMEpoWVhjS01YaHNSbk5yVG5ZNVlVRXhNMWxXVXpZMWRYWlBXVzUyUzJOa1YxVkRXamx5VW1SdVREWlNiMUJUVm1OQlFYQnZlU3RPY2xOaU5DOXBXVFU1V2tKSFJncEpkRmQwYm1sdlZURTVhVmxzT1ZoRGVtaDZaakF6VDB4NlVqaGpOVFJFT0VFemFXZzNOR3AwU0hKUksweEpjWFJ0UnpScGNYTk9kbWhCWXpoREwzSmxDa1JQYm5VNVIxSnRSMmR1SzFkbEszbFlkMDVGVDBGTFdVcERZV1l2Y25jM1NYRk1PSEpZZDJOWVltcHBia1ZhUlVZclZUMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0KICAgIGNsaWVudC1rZXktZGF0YTogTFMwdExTMUNSVWRKVGlCU1UwRWdVRkpKVmtGVVJTQkxSVmt0TFMwdExRcE5TVWxGYjNkSlFrRkJTME5CVVVWQmQwbE1PVWwxTTFSMGJXOWpUSEp0YlZReFQzZGpOazF0Y1ZGSE4zcFZOemhvVkVGSVNYcEtUVmh2VFRCaFNrVlNDaTlyV0V4dVNFOUtUUzgwY0hndlRsZFdhVVpIZFdsamFXdFlkVGQyVVRCU2F6ZHFXREpOUVZOQ00yWk5MeXN6T1hobFRHNHdUV1JKVldKWVEwaGFVVmtLTmxWNlNWRnJSbUZWYnk5YVQzZHVjekpOY0RGMVMxSlJiMGhFZGpsVVNUZEJiRTVyVWsxMFRrUjZUVE14VTBGaksxQkhSVWhHZEc4dmJVRkdUVU01VUFwYWFHMW5aa2h5U201dVZFRlVhMWd5WTNwUFdsVm1Nak40Ukc4eFkwVnljbm92WWsxemRFTklVR0ZuTlUxS1RubDROMFl3ZUdGRFR6UjBMMG8yTjJsbUNrYzBVMlkxUm5WalZVdExPSFZPVGtaUVdWUkdPRWRqYjBaYWVGcEROVTAxYjBORVJrVTNWRVo0Wm5WbU4yZFlZa2cwYzNwUU5IZEZXalpQU2tod09IRUtRa1p0VHpKT1JYQjBhekE1UkdabE1uQk5OV3B6WkVoeGFrNXhhUzlSYjBGQk9VdzRRMUZKUkVGUlFVSkJiMGxDUVVaTGJYRnNiVnAwUVVOdlRTdDBXQXB3TTBJNVdrMDVkelUwVFN0RVJYaE5UV2g0V1dveVpuTkRNU3N3WjNkaE1UWm1lVFpMU0ROSFIwMTZjWFpVVG5OYVRTOWlkalJ5YVdwSVVHSndURWMwQ2pjMlpqRm1lVFFyTkVRMk1tbzFkamx4UzNWVFJFdGlWazlHV210aE5pOWFVMjk1TTJVeU55dHpaa2R4WTNOQ2JrNVliWEZEY25FMFUxTnNiemcwZWxvS1FWQTJlRzlQV0hOU2RYaFZkVUUzV0d3M2FrWnpSWFZuWWpCYWRWbHZURGRWUmtkNlpXTndWM1p5TWpkUFpGQktiVVZDTTNOMWRqUXpTa0l6T0dGcVdRcG9SWEI1WkVaNlEyRlVhV2xGWTFwQlpWVjJVVUl5TkVsU01IUTVPVEJaVUU5VmEyaEJXbmhCYVRad1FVTnBXRTgwUlZGVk5XdDJLMmh0U3prck1tbzFDbmhZYVRGdVMwZDNiak0zYVd4cE1URjBUMDF0T0N0NFNYUkxXazVFYmtGNFFUZGtOVlFyV1dSb1UzVndOSGhHUjFwa01rMTVlbFpHY0RaWGNGSnNXSElLVFROb01sZHdSVU5uV1VWQk1tSmlWakpJZDAxRVNVcG1kbFZRY0cxSmJteG1kRVZLTjBOd2FWY3dZbFo1YVVJNE0yTndkMHRuYm5GdWNYcEliazFaU3dwQ1pTdFdjMEUyVnpkTVEyRk5Va040UWtGaFluWmpVbnB2UWxFM1JFWnVTV2xJUlRkTVNVMVlUakZ5ZVVOcVZrRXlhbk00TlRORk1HSkhOazFuVEcxTkNteFhVRUZUY2xBd1VHbG9aSGszV0VKd1lXVkpaa2hwUW1neU4xbE9ORmgwVHpkVlJGVjNSMHRXV1Rsa2FuVkRRMnBuZWs1R1NqQkRaMWxGUVRSc01sTUtOV3QyU0hWalJrWjRabXhFYzFJdmJTOUhWbGhSVjNCSlJEWlNjRVZVYWs1bVZYUnlPRFpKVHpWWmREbEVMMXBRT1hSUE1rSlFkM2REZWxsc1Rqa3pNZ3A0VlROTFRrMUVabGt4ZGtGWmNFdGxlSEZKTldJck5IaElkVEpaVEVjdlVFbHNVM1JHVDBOTmRYVnhibFZCZEdkeFNVZHNhR052YzNSTGRFdGFSRzB5Q25KV1VEQlpNVEpvTkZaemVrMXdUbTh6V0dwb2FXeERTaTlrVUZkVVFVdElkbVYyUTNreE1FTm5XVUpHV1RscVZXNTJWRmxLT1ZkTU5WWkdWM0JPZWxrS2FIQmxOMEpsWkVSWlJtaFVhUzlrUVZkWU1WZGhUVXN5ZDBOeUt6bE1MMDVHU1dWS1JGb3hZelIzZWtOQ2NXWnJkVXBhTmpkV2NFMDRlVmNyTUdZM2R3cFFaV0V5VlZSSVpFZHpaRFpMVnpjMlNERmtVMFJQY2tGb1NuVlVhVFZCWWt4VFptdHdNbG8xVTFSU1lqaFNieTlUYURWbFZHRnZOMGRGUldoelZGaHFDbWxrV0RsYWMyaEpZazlHVFRSS1J6YzFTMmxaYjFGTFFtZEhZMEZvTUVOT2FWQk5iMVU1VW5sNVR5dElRV0pqYkdOa0wwNVBZamhOV1RsaE0xUjVTRTRLVDFCRWFXTXZOMVpUZGtsQ1pGZzVkRGxIZDNWVFRIQkNVMEYzTUhwMk5GZHRTVlZMYjJ4ME1uVTBUR1pFY1hBMU1VMU1kVEJ4UlhSSlkzQlBhMmxWWmdvdlMxZzRVekl3WlVzd1dFdzVORFpXWkRkcU5rNTJPVFl4WkROdVQxRXlSRzU2WlRkMWJGcDFRbXQ2YTAxS2NHVlVPWEE1VEZZd00ydEdTMFE0UkhCdENsWm1kblJCYjBkQ1FVcFhWbEJPY204dllqVnRiV0Z4TmxKNE1YbHRhVkpPVUZkSVdrTm1aelprWkRCQ1FtdEhjWEpNYzBwWllXWmFPR3BTU0hnM1Ywc0tRMWRpWkdZeGVrVm1OazFCTVhJNWVVVXJNa3hDZVdkdk9GcFhSVTFpUjJkalYxRkRUMFJST0hKa1FXMVJUazltVjA5TFFsaGlOV1JRVkdZclNXRklOUXBOWm5sMlYxRjZTMFpLYmtKMWFVZzVaV3cwYlRaS2MwOVVkeTlqT1UxS2EzTTVjMFZNY2s4MmVVRk9NemhIVWpobFMyVjJDaTB0TFMwdFJVNUVJRkpUUVNCUVVrbFdRVlJGSUV0RldTMHRMUzB0Q2c9PQo= -kind: Secret -metadata: - labels: - cluster.x-k8s.io/cluster-name: workload-cluster - name: test-cluster-kubeconfig - namespace: target-infra - ownerReferences: - - apiVersion: controlplane.cluster.x-k8s.io/v1alpha3 - blockOwnerDeletion: true - controller: true - kind: KubeadmControlPlane - name: workload-cluster-control-plane - uid: 8f4112d0-83f5-44ef-9fc4-041e0eeee08e -type: Opaque \ No newline at end of file diff --git a/pkg/cluster/checkexpiration/testdata/node.yaml b/pkg/cluster/checkexpiration/testdata/node.yaml deleted file mode 100644 index 8e1b99c8d..000000000 --- a/pkg/cluster/checkexpiration/testdata/node.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: Node -metadata: - annotations: - cert-expiration: "{ admin.conf: Aug 06, {{year}} 12:36 UTC },{ apiserver: Aug 06, {{year}} 12:36 UTC },{ apiserver-etcd-client: Aug 06, {{year}} 12:36 UTC },{ apiserver-kubelet-client: Aug 06, {{year}} 12:36 UTC },{ controller-manager.conf: Aug 06, {{year}} 12:36 UTC },{ etcd-healthcheck-client: Aug 06, {{year}} 12:36 UTC },{ etcd-peer: Aug 06, {{year}} 12:36 UTC },{ etcd-server: Aug 06, {{year}} 12:36 UTC },{ front-proxy-client: Aug 06, {{year}} 12:36 UTC },{ scheduler.conf: Aug 06, {{year}} 12:36 UTC },{ ca: Aug 04, {{year}} 12:36 UTC },{ etcd-ca: Aug 04, {{year}} 12:36 UTC },{ front-proxy-ca: Aug 04, {{year}} 12:36 UTC }" - name: test-node \ No newline at end of file diff --git a/pkg/cluster/checkexpiration/testdata/tls-secret.yaml b/pkg/cluster/checkexpiration/testdata/tls-secret.yaml deleted file mode 100644 index 4e98bd081..000000000 --- a/pkg/cluster/checkexpiration/testdata/tls-secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5ekNDQWJPZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01Ea3dNakV3TURjME9Wb1hEVE13TURnek1URXdNVEkwT1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTHdVCkErWWRNcWF0clI4WWVHcEVYTFlIeVZHRi9Na0g1L3FqWEdpcmxITCtiNEhXYmg2enkvSWY3N1MzNWhnTzFPSkcKcG5wdmk5ck5ISEMyV3hNblVUOFVxSE1lYjNyb0phMlFpME1sNGlNTFFncTd0TGhLQ04zTnFwYmk5OEJ4d1VxSAo3eGkzWmU3WEZ2NVJyRlpFa2hicW9ycVJRZzg0cHdRTTNvMkh1NmJSWElETjc5bnVMV3piZ0pYUzhwMytnZHFuCkxIa3owOGN4VkxxZmJPOFMxOFEwdWJUbnY3RjVBblBPZkhJY2xyR2h3MFVUZXRWZGxuVmNISnRpZ29xYjBBdysKOUY5WkNaN0ZZUzE2eEJ0L0N0OHpKZEQ3MEFGZ2NMRm4vSTJlSG05bTFSK0FISWxJU3ZLbzl0OVBtekJxWS9tbgpHOUFoektYSlBTSHRPbEJqMXBzQ0F3RUFBYU1tTUNRd0RnWURWUjBQQVFIL0JBUURBZ0trTUJJR0ExVWRFd0VCCi93UUlNQVlCQWY4Q0FRQXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQVAzLzdmYzhBNlFjWU5CUnQxdDRWQ0QKRnYwMDB0Q09mVjVxdnNOU2RQYTRZY2NaTDBmT2dUVmtNbGczbTVRMmVKUkpVTHR5V0NHQ3BNcHRFR2duMGI3eQpIWkhtWkpkZjZBN0twbFRqQWxyemRDOUpPTVBrQUtjaXhNcWhPcDFxdTI0dHl5eEZQZWdMeTE2SU5ZMGl5ZzI2ClJhYkowREdQcTNZUitwZHphRkZ2YUN6bk1yTGtDckJpV0xvUmdrK2xaT3NoUU1EaHl4Y1crQW5mcHRINHlxM2YKZjhhdFZPVGprMGVIYVlQUlNKSlFlM0RLOXFEY0V4bVFib1orM1NLYXRtK0RNMWVieE5CNlVKaGdYWEluZ3dQQwp4czV0azJkUGlJaXZldXZYbGVqTkZ1c0RzekV1NU9ZN08xVzZNSUdmNlBPQ3JmSzk0S3JhVWVhWng4bUlRcE09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K - ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5ekNDQWJPZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01Ea3dNakV3TURjME9Wb1hEVE13TURnek1URXdNVEkwT1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTHdVCkErWWRNcWF0clI4WWVHcEVYTFlIeVZHRi9Na0g1L3FqWEdpcmxITCtiNEhXYmg2enkvSWY3N1MzNWhnTzFPSkcKcG5wdmk5ck5ISEMyV3hNblVUOFVxSE1lYjNyb0phMlFpME1sNGlNTFFncTd0TGhLQ04zTnFwYmk5OEJ4d1VxSAo3eGkzWmU3WEZ2NVJyRlpFa2hicW9ycVJRZzg0cHdRTTNvMkh1NmJSWElETjc5bnVMV3piZ0pYUzhwMytnZHFuCkxIa3owOGN4VkxxZmJPOFMxOFEwdWJUbnY3RjVBblBPZkhJY2xyR2h3MFVUZXRWZGxuVmNISnRpZ29xYjBBdysKOUY5WkNaN0ZZUzE2eEJ0L0N0OHpKZEQ3MEFGZ2NMRm4vSTJlSG05bTFSK0FISWxJU3ZLbzl0OVBtekJxWS9tbgpHOUFoektYSlBTSHRPbEJqMXBzQ0F3RUFBYU1tTUNRd0RnWURWUjBQQVFIL0JBUURBZ0trTUJJR0ExVWRFd0VCCi93UUlNQVlCQWY4Q0FRQXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBQVAzLzdmYzhBNlFjWU5CUnQxdDRWQ0QKRnYwMDB0Q09mVjVxdnNOU2RQYTRZY2NaTDBmT2dUVmtNbGczbTVRMmVKUkpVTHR5V0NHQ3BNcHRFR2duMGI3eQpIWkhtWkpkZjZBN0twbFRqQWxyemRDOUpPTVBrQUtjaXhNcWhPcDFxdTI0dHl5eEZQZWdMeTE2SU5ZMGl5ZzI2ClJhYkowREdQcTNZUitwZHphRkZ2YUN6bk1yTGtDckJpV0xvUmdrK2xaT3NoUU1EaHl4Y1crQW5mcHRINHlxM2YKZjhhdFZPVGprMGVIYVlQUlNKSlFlM0RLOXFEY0V4bVFib1orM1NLYXRtK0RNMWVieE5CNlVKaGdYWEluZ3dQQwp4czV0azJkUGlJaXZldXZYbGVqTkZ1c0RzekV1NU9ZN08xVzZNSUdmNlBPQ3JmSzk0S3JhVWVhWng4bUlRcE09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K - tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBdkJRRDVoMHlwcTJ0SHhoNGFrUmN0Z2ZKVVlYOHlRZm4rcU5jYUt1VWN2NXZnZFp1CkhyUEw4aC92dExmbUdBN1U0a2FtZW0rTDJzMGNjTFpiRXlkUlB4U29jeDV2ZXVnbHJaQ0xReVhpSXd0Q0NydTAKdUVvSTNjMnFsdUwzd0hIQlNvZnZHTGRsN3RjVy9sR3NWa1NTRnVxaXVwRkNEemluQkF6ZWpZZTdwdEZjZ00zdgoyZTR0Yk51QWxkTHluZjZCMnFjc2VUUFR4ekZVdXA5czd4TFh4RFM1dE9lL3NYa0NjODU4Y2h5V3NhSERSUk42CjFWMldkVndjbTJLQ2lwdlFERDcwWDFrSm5zVmhMWHJFRzM4SzN6TWwwUHZRQVdCd3NXZjhqWjRlYjJiVkg0QWMKaVVoSzhxajIzMCtiTUdwaithY2IwQ0hNcGNrOUllMDZVR1BXbXdJREFRQUJBb0lCQUJESkZJUUFEUm8xRytOUAprc2VoTEVrT3J0ZjR4bFBHd2R4cm9mNnhlWUU5MWdQWGVHS0RGMnVYa0JRbjZZQXlLcXU3TkhadTZDTng5TnpXCldaQi9ETkE5YnI4L2N5R2NBR2phSXFPdWlOMHB6dzRZTEl2YUI2cU1CWEtMOVNLV3hISjdhVXBpYTlXQ0dzbzkKemN5eE4veVZta3BlVm0vM1ZXaVdJWEt1TDRBMnZmaHJjbHdVc3NwNFA4Q1dZUFZVbExOT0JsYlQrNy9qQ1RubwpyRDVYcjNDVEFiUmNBNEdvcGx1ZFphaUtqY2FpZXpYeHdTaGFmWkFURWJJM0xwejZSTnRBMDRLbjEyR0JLRDQwCk1zV21xUU1zRE4zUkgveFdKbnp5TFVSTGVlcDdRUTdXQytZYmRmdFpTZ3FKUkRNYmw1aHdwUXRLeFBoUmhHakcKSGEwWUVIa0NnWUVBNkdjSCtzUzczd3FwUmloZXJ3aFVEY21Na3VEZDBiMkEyWlhpcjBRUStKUXZrVHVSd2NZVgpHdDU3Zm40UnBybFUwNzByMTZRQzcrdTU0OVdQVlFpbExvMURvVUVHMC9QVEhSV2ZQVjBtOWh2cGF2akdtMG1zCmpmd20rMXZ1WGFiS0RMblg0dXcxQ0JKSU9vdmJ6RnhrTGJWZk91YVhrN1BhckhiOGdHRHR2ZjhDZ1lFQXp5elcKZ3VtT25tMmVNYy80ZHJmMDI2Q1doZk1ZMXF0VnFEb2s4anVlMVlEaVdhZEMwOGo3VEVoQ1dTQUZPOC9ZOVBDRApKQ3cvcGpUZThERlRLZEw3d2t5ZVUrS2JJRFRLNHJ5clMzenZmV3Q4Zjg2ZmU5SVd3L3FDaEN2cm5tSTdNeS8rCkowL1BCZFFGL0xkeS9PWlVGcW9wblJNS1VnVTJXMWhxRTJOcUgyVUNnWUExV0NqeHU2U3YvcDk2Tmh2OXF6aTMKN1hKeDZHR2lHaEJ3WVVJbUhzYVNlRmt1eWZDYi9ONnRTekluaDhKL2RYenVHVGJ1Q1h5UEc1bVFuVjJJRkRMdQpLNGpCZzg2UWFpQWtSZWxHU1pKKzNVdEh2WkRBNWpsUVlmZUVyTVpiQXNUUUJQeHozdW9SVHpqN0QwMUZiRk9tClZrSmtuN2RkTk9SVnYvNFhiYWhFZXdLQmdENURTM1NzbktBZ2NacW0xaFZYMDg3dHhFOGRjQ21UOUhwS2Z6QU4KbXY2dmJWZGtYVUVvOWQxSEdpbU81Z1BEdzRCWmlCQW0vRG9IU2JrR0dlaEg4RUhFcFJDdzJjNGtENVYwL2tZQgpsamdyUlk5am1hcXN5UXE5RHR5S0ZwWFREOWVpWk0rTHZMd1RySGoyNlNmNFVPMCsxcUxPUmh2QVZVVytuS0tYCkRoM0JBb0dBRjVEU0R2U2ltVjhtaHFWbjc2M1dwOUJXN0RwbTAxTE5qMHRZbVFhZVpyK3VoZ1BKdW5SYzhBUE4KWXRudjlsY04wSkx1MzhWaWI3eVFzVmgvVUsvYVdwS2w1YUZzTXdRMVNJTUJXUTVEeVR0VEE0VGZXQzFUSUYxUQpuempLd0NSTHBaZ1F6ZkNCaTBIT3doZ0pUaDZBNng4SG1hYXU5ZmZLdE90SUpkTUZ5d1E9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== -kind: Secret -metadata: - labels: - cluster.x-k8s.io/cluster-name: test-cluster - name: test-cluster-etcd - namespace: target-infra -type: Opaque \ No newline at end of file