From e1cd0967d5085fa47f32ad51bdb9d03b69b05334 Mon Sep 17 00:00:00 2001 From: Jared Watts Date: Sun, 2 Dec 2018 19:37:28 -0800 Subject: [PATCH] compute: parse raw kubeconfig data into resource credentials secret keys format Signed-off-by: Jared Watts --- pkg/apis/compute/v1alpha1/config.go | 99 ++++++++++++++++++++++++ pkg/apis/compute/v1alpha1/config_test.go | 72 +++++++++++++++++ pkg/apis/core/v1alpha1/resource.go | 4 +- 3 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 pkg/apis/compute/v1alpha1/config.go create mode 100644 pkg/apis/compute/v1alpha1/config_test.go diff --git a/pkg/apis/compute/v1alpha1/config.go b/pkg/apis/compute/v1alpha1/config.go new file mode 100644 index 0000000..e17eb6d --- /dev/null +++ b/pkg/apis/compute/v1alpha1/config.go @@ -0,0 +1,99 @@ +/* +Copyright 2018 The Crossplane Authors. + +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 + + http://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 v1alpha1 + +import ( + "fmt" + + corev1alpha1 "github.com/crossplaneio/crossplane/pkg/apis/core/v1alpha1" + "github.com/ghodss/yaml" + kubectlv1 "k8s.io/client-go/tools/clientcmd/api/v1" +) + +func ParseKubeconfig(rawKubeconfig []byte) (map[string][]byte, error) { + // unmarshal the raw kubeconfig into a strongly typed kubeconfig struct + kubeconfig := &kubectlv1.Config{} + if err := yaml.Unmarshal(rawKubeconfig, kubeconfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal kubeconfig: %+v", err) + } + + if len(kubeconfig.Contexts) == 0 { + // no contexts in the kubeconfig, we can't return anything meaningful + return nil, fmt.Errorf("no contexts found in kubeconfig") + } + + // find the current context for this kubeconfig + var currentContext *kubectlv1.NamedContext + if kubeconfig.CurrentContext == "" { + // no current context set, just use the first context + currentContext = &kubeconfig.Contexts[0] + } else { + // current context is set, find the matching named context + for i, ctx := range kubeconfig.Contexts { + if kubeconfig.CurrentContext == ctx.Name { + currentContext = &kubeconfig.Contexts[i] + break + } + } + } + + if currentContext == nil { + // failed to find a current context + return nil, fmt.Errorf("failed to find current context in kubeconfig") + } + + // find the cluster for the current context + var cluster *kubectlv1.NamedCluster + for i, c := range kubeconfig.Clusters { + if currentContext.Context.Cluster == c.Name { + cluster = &kubeconfig.Clusters[i] + break + } + } + + if cluster == nil { + // failed to find the current context's cluster + return nil, fmt.Errorf("failed to find cluster %s in kubeconfig", currentContext.Context.Cluster) + } + + // find the auth info for the current context + var authInfo *kubectlv1.NamedAuthInfo + for i, ai := range kubeconfig.AuthInfos { + if currentContext.Context.AuthInfo == ai.Name { + authInfo = &kubeconfig.AuthInfos[i] + break + } + } + + if authInfo == nil { + // failed to find the current context's auth info + return nil, fmt.Errorf("failed to find auth info %s in kubeconfig", currentContext.Context.AuthInfo) + } + + // we have a context, a cluster, and an auth info. let's fill out the cluster resource map + kubeconfigData := map[string][]byte{ + corev1alpha1.ResourceCredentialsSecretEndpointKey: []byte(cluster.Cluster.Server), + corev1alpha1.ResourceCredentialsSecretUserKey: []byte(authInfo.AuthInfo.Username), + corev1alpha1.ResourceCredentialsSecretPasswordKey: []byte(authInfo.AuthInfo.Password), + corev1alpha1.ResourceCredentialsSecretCAKey: cluster.Cluster.CertificateAuthorityData, + corev1alpha1.ResourceCredentialsSecretClientCertKey: authInfo.AuthInfo.ClientCertificateData, + corev1alpha1.ResourceCredentialsSecretClientKeyKey: authInfo.AuthInfo.ClientKeyData, + corev1alpha1.ResourceCredentialsTokenKey: []byte(authInfo.AuthInfo.Token), + } + + return kubeconfigData, nil +} diff --git a/pkg/apis/compute/v1alpha1/config_test.go b/pkg/apis/compute/v1alpha1/config_test.go new file mode 100644 index 0000000..3ed117e --- /dev/null +++ b/pkg/apis/compute/v1alpha1/config_test.go @@ -0,0 +1,72 @@ +/* +Copyright 2018 The Crossplane Authors. + +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 + + http://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 v1alpha1 + +import ( + "testing" + + corev1alpha1 "github.com/crossplaneio/crossplane/pkg/apis/core/v1alpha1" + "github.com/onsi/gomega" +) + +const ( + // This data format is the general kubectl config (kubeconfig) format, but it specifically + // came from the Azure AKS ListClusterAdminCredentials API: + // https://docs.microsoft.com/en-us/rest/api/aks/managedclusters/listclusteradmincredentials + // The values in it have all been replaced with fake data + mockRawClusterCredentialsData = `apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: Y2VydGlmaWNhdGUtYXV0aG9yaXR5LWRhdGEtdmFsdWU= + server: https://crossplane-aks-55e038af.hcp.westus2.azmk8s.io:443 + name: aks-c7f893e4-d903-402c-ba60-8be6bbeac6a3 +contexts: +- context: + cluster: aks-c7f893e4-d903-402c-ba60-8be6bbeac6a3 + user: clusterAdmin_rg-123_aks-c7f893e4-d903-402c-ba60-8be6bbeac6a3 + namespace: foo + name: aks-c7f893e4-d903-402c-ba60-8be6bbeac6a3 +current-context: aks-c7f893e4-d903-402c-ba60-8be6bbeac6a3 +kind: Config +preferences: {} +users: +- name: clusterAdmin_rg-123_aks-c7f893e4-d903-402c-ba60-8be6bbeac6a3 + user: + client-certificate-data: Y2xpZW50LWNlcnRpZmljYXRlLWRhdGEtdmFsdWU= + client-key-data: Y2xpZW50LWtleS1kYXRhLXZhbHVl + token: 799e6a56219da5ff09e3b41b1dd08f3f +` +) + +func TestParseKubeconfig(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + // the unmarshal of the raw data will decode the base64 encoded values, so we expect them in plain-text + expectedKubeconfigData := map[string][]byte{ + corev1alpha1.ResourceCredentialsSecretEndpointKey: []byte("https://crossplane-aks-55e038af.hcp.westus2.azmk8s.io:443"), + corev1alpha1.ResourceCredentialsSecretUserKey: []byte(""), + corev1alpha1.ResourceCredentialsSecretPasswordKey: []byte(""), + corev1alpha1.ResourceCredentialsSecretCAKey: []byte("certificate-authority-data-value"), + corev1alpha1.ResourceCredentialsSecretClientCertKey: []byte("client-certificate-data-value"), + corev1alpha1.ResourceCredentialsSecretClientKeyKey: []byte("client-key-data-value"), + corev1alpha1.ResourceCredentialsTokenKey: []byte("799e6a56219da5ff09e3b41b1dd08f3f"), + } + + kubeconfigData, err := ParseKubeconfig([]byte(mockRawClusterCredentialsData)) + g.Expect(err).NotTo(gomega.HaveOccurred()) + g.Expect(kubeconfigData).To(gomega.Equal(expectedKubeconfigData)) +} diff --git a/pkg/apis/core/v1alpha1/resource.go b/pkg/apis/core/v1alpha1/resource.go index ceea58c..9ce46cf 100644 --- a/pkg/apis/core/v1alpha1/resource.go +++ b/pkg/apis/core/v1alpha1/resource.go @@ -31,10 +31,10 @@ const ( ResourceCredentialsSecretClientCertKey = "clientCert" // ResourceCredentialsSecretClientKeyKey is the key inside a connection secret for the client key ResourceCredentialsSecretClientKeyKey = "clientKey" - // ResourceCredentialsSecretClusterConfigFile is the key inside a connection secret for the full cluster configuration file - ResourceCredentialsSecretClusterConfigFile = "clusterConfig" // ResourceCredentialsTokenKey is the key inside a connection secret for the bearer token value ResourceCredentialsTokenKey = "token" + // ResourceCredentialsSecretKubeconfigFileKey is the key inside a connection secret for the full kubeconfig file + ResourceCredentialsSecretKubeconfigFileKey = "kubeconfig" ) // Resource defines operations supported by managed resource