ソースを参照

Merge pull request #90 from porter-dev/beta.2.kubectl-oidc-plugin

oidc plugin cert population
sunguroku 5 年 前
コミット
a238b9f3a9

+ 3 - 2
cli/cmd/generate.go

@@ -4,7 +4,8 @@ import (
 	"fmt"
 	"path/filepath"
 
-	"github.com/porter-dev/porter/internal/kubernetes"
+	"github.com/porter-dev/porter/internal/kubernetes/local"
+
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/util/homedir"
 
@@ -63,7 +64,7 @@ func init() {
 }
 
 func generate(kubeconfigPath string, output string, print bool, contexts []string) error {
-	conf, err := kubernetes.GetConfigFromHostWithCertData(kubeconfigPath, contexts)
+	conf, err := local.GetConfigFromHostWithCertData(kubeconfigPath, contexts)
 
 	if err != nil {
 		return err

+ 4 - 164
internal/kubernetes/kubeconfig.go

@@ -1,16 +1,9 @@
 package kubernetes
 
 import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-
 	"github.com/porter-dev/porter/internal/models"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/tools/clientcmd/api"
-	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
-	"k8s.io/client-go/util/homedir"
 )
 
 // GetRestrictedClientConfigFromBytes returns a clientcmd.ClientConfig from a raw kubeconfig,
@@ -41,7 +34,7 @@ func GetRestrictedClientConfigFromBytes(
 	copyConf.CurrentContext = contextName
 
 	// put allowed clusters in a map
-	aContextMap := createAllowedContextMap(allowedContexts)
+	aContextMap := CreateAllowedContextMap(allowedContexts)
 
 	context, ok := rawConf.Contexts[contextName]
 
@@ -103,7 +96,7 @@ func toContexts(rawConf *api.Config, allowedContexts []string) []models.Context
 	contexts := make([]models.Context, 0)
 
 	// put allowed clusters in map
-	aContextMap := createAllowedContextMap(allowedContexts)
+	aContextMap := CreateAllowedContextMap(allowedContexts)
 
 	// iterate through contexts and switch on selected
 	for name, context := range rawConf.Contexts {
@@ -133,8 +126,8 @@ func toContexts(rawConf *api.Config, allowedContexts []string) []models.Context
 	return contexts
 }
 
-// createAllowedContextMap creates a dummy map from context name to context name
-func createAllowedContextMap(contexts []string) map[string]string {
+// CreateAllowedContextMap creates a dummy map from context name to context name
+func CreateAllowedContextMap(contexts []string) map[string]string {
 	aContextMap := make(map[string]string)
 
 	for _, context := range contexts {
@@ -143,156 +136,3 @@ func createAllowedContextMap(contexts []string) map[string]string {
 
 	return aContextMap
 }
-
-// GetConfigFromHostWithCertData gets the kubeconfig using default options set on the host:
-// the kubeconfig can either be retrieved from a specified path or an environment variable.
-// This function only outputs a clientcmd that uses the allowedContexts.
-//
-// This function also populates all of the certificate data that's specified as a filepath.
-func GetConfigFromHostWithCertData(kubeconfigPath string, allowedContexts []string) (clientcmd.ClientConfig, error) {
-	envVarName := clientcmd.RecommendedConfigPathEnvVar
-
-	if kubeconfigPath != "" {
-		if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
-			// the specified kubeconfig does not exist so fallback to other options
-			kubeconfigPath = ""
-		}
-	}
-
-	if kubeconfigPath == "" && os.Getenv(envVarName) == "" {
-		if home := homedir.HomeDir(); home != "" {
-			kubeconfigPath = filepath.Join(home, ".kube", "config")
-		}
-	}
-
-	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
-	loadingRules.ExplicitPath = kubeconfigPath
-
-	clientConf := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
-	rawConf, err := clientConf.RawConfig()
-
-	if err != nil {
-		return nil, err
-	}
-
-	populateCertificateRefs(&rawConf)
-
-	if len(allowedContexts) == 0 {
-		allowedContexts = []string{rawConf.CurrentContext}
-
-		if allowedContexts[0] == "" {
-			return nil, fmt.Errorf("at least one context must be specified")
-		}
-	}
-
-	res, err := stripAndValidateClientContexts(&rawConf, allowedContexts[0], allowedContexts)
-
-	if err != nil {
-		return nil, err
-	}
-
-	return res, nil
-}
-
-// GetRestrictedClientConfigFromBytes returns a clientcmd.ClientConfig from a raw kubeconfig,
-// a context name, and the set of allowed contexts.
-func stripAndValidateClientContexts(
-	rawConf *clientcmdapi.Config,
-	currentContext string,
-	allowedContexts []string,
-) (clientcmd.ClientConfig, error) {
-	// grab a copy to get the pointer and set clusters, authinfos, and contexts to empty
-	copyConf := rawConf.DeepCopy()
-
-	copyConf.Clusters = make(map[string]*api.Cluster)
-	copyConf.AuthInfos = make(map[string]*api.AuthInfo)
-	copyConf.Contexts = make(map[string]*api.Context)
-	copyConf.CurrentContext = currentContext
-
-	// put allowed clusters in a map
-	aContextMap := createAllowedContextMap(allowedContexts)
-
-	for contextName, context := range rawConf.Contexts {
-		userName := context.AuthInfo
-		clusterName := context.Cluster
-		authInfo, userFound := rawConf.AuthInfos[userName]
-		cluster, clusterFound := rawConf.Clusters[clusterName]
-
-		// make sure the cluster is "allowed"
-		_, isAllowed := aContextMap[contextName]
-
-		if userFound && clusterFound && isAllowed {
-			copyConf.Clusters[clusterName] = cluster
-			copyConf.AuthInfos[userName] = authInfo
-			copyConf.Contexts[contextName] = context
-		}
-	}
-
-	// validate the copyConf and create a ClientConfig
-	err := clientcmd.Validate(*copyConf)
-
-	if err != nil {
-		return nil, err
-	}
-
-	clientConf := clientcmd.NewDefaultClientConfig(*copyConf, &clientcmd.ConfigOverrides{})
-
-	return clientConf, nil
-}
-
-func populateCertificateRefs(config *clientcmdapi.Config) {
-	for _, cluster := range config.Clusters {
-		refs := clientcmd.GetClusterFileReferences(cluster)
-		for _, str := range refs {
-			// only write certificate if the file reference is CA
-			if *str != cluster.CertificateAuthority {
-				break
-			}
-
-			fileBytes, err := ioutil.ReadFile(*str)
-
-			if err != nil {
-				continue
-			}
-
-			cluster.CertificateAuthorityData = fileBytes
-			cluster.CertificateAuthority = ""
-		}
-	}
-
-	for _, authInfo := range config.AuthInfos {
-		refs := clientcmd.GetAuthInfoFileReferences(authInfo)
-		for _, str := range refs {
-			if *str == "" {
-				continue
-			}
-
-			var refType int
-
-			if authInfo.ClientCertificate == *str {
-				refType = 0
-			} else if authInfo.ClientKey == *str {
-				refType = 1
-			} else if authInfo.TokenFile == *str {
-				refType = 2
-			}
-
-			fileBytes, err := ioutil.ReadFile(*str)
-
-			if err != nil {
-				continue
-			}
-
-			if refType == 0 {
-				authInfo.ClientCertificateData = fileBytes
-				authInfo.ClientCertificate = ""
-			} else if refType == 1 {
-				authInfo.ClientKeyData = fileBytes
-				authInfo.ClientKey = ""
-			} else if refType == 2 {
-				authInfo.Token = string(fileBytes)
-				authInfo.TokenFile = ""
-			}
-		}
-	}
-}

+ 29 - 0
internal/kubernetes/kubeconfig_test.go

@@ -265,3 +265,32 @@ contexts:
 users:
   - name: test-admin
 `
+
+const oidcPlugin string = `
+apiVersion: v1
+kind: Config
+preferences: {}
+current-context: context-test
+clusters:
+- cluster:
+    server: https://localhost
+  name: cluster-test
+contexts:
+- context:
+    cluster: cluster-test
+    user: test-admin
+  name: context-test
+users:
+  - name: test-admin
+  - name: test-admin
+  user:
+    auth-provider:
+      config:
+        client-id: sampleclientid
+        client-secret: sampleclientsecret
+        id-token: IDTOKEN
+        idp-issuer-url: https://login.example.com/
+		refresh-token: REFRESHTOKEN
+		idp-certificate-authority: /example/file/on/system.pem
+      name: oidc
+`

+ 189 - 0
internal/kubernetes/local/kubeconfig.go

@@ -0,0 +1,189 @@
+// +build cli
+
+package local
+
+import (
+	"encoding/base64"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"github.com/porter-dev/porter/internal/kubernetes"
+
+	"k8s.io/client-go/tools/clientcmd"
+	"k8s.io/client-go/tools/clientcmd/api"
+	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
+	"k8s.io/client-go/util/homedir"
+)
+
+// GetConfigFromHostWithCertData gets the kubeconfig using default options set on the host:
+// the kubeconfig can either be retrieved from a specified path or an environment variable.
+// This function only outputs a clientcmd that uses the allowedContexts.
+//
+// This function also populates all of the certificate data that's specified as a filepath.
+func GetConfigFromHostWithCertData(kubeconfigPath string, allowedContexts []string) (clientcmd.ClientConfig, error) {
+	envVarName := clientcmd.RecommendedConfigPathEnvVar
+
+	if kubeconfigPath != "" {
+		if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
+			// the specified kubeconfig does not exist so fallback to other options
+			kubeconfigPath = ""
+		}
+	}
+
+	if kubeconfigPath == "" && os.Getenv(envVarName) == "" {
+		if home := homedir.HomeDir(); home != "" {
+			kubeconfigPath = filepath.Join(home, ".kube", "config")
+		}
+	}
+
+	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
+	loadingRules.ExplicitPath = kubeconfigPath
+
+	clientConf := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
+	rawConf, err := clientConf.RawConfig()
+
+	if err != nil {
+		return nil, err
+	}
+
+	populateCertificateRefs(&rawConf)
+	populateOIDCPluginCerts(&rawConf)
+
+	if len(allowedContexts) == 0 {
+		allowedContexts = []string{rawConf.CurrentContext}
+
+		if allowedContexts[0] == "" {
+			return nil, fmt.Errorf("at least one context must be specified")
+		}
+	}
+
+	res, err := stripAndValidateClientContexts(&rawConf, allowedContexts[0], allowedContexts)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return res, nil
+}
+
+// GetRestrictedClientConfigFromBytes returns a clientcmd.ClientConfig from a raw kubeconfig,
+// a context name, and the set of allowed contexts.
+func stripAndValidateClientContexts(
+	rawConf *clientcmdapi.Config,
+	currentContext string,
+	allowedContexts []string,
+) (clientcmd.ClientConfig, error) {
+	// grab a copy to get the pointer and set clusters, authinfos, and contexts to empty
+	copyConf := rawConf.DeepCopy()
+
+	copyConf.Clusters = make(map[string]*api.Cluster)
+	copyConf.AuthInfos = make(map[string]*api.AuthInfo)
+	copyConf.Contexts = make(map[string]*api.Context)
+	copyConf.CurrentContext = currentContext
+
+	// put allowed clusters in a map
+	aContextMap := kubernetes.CreateAllowedContextMap(allowedContexts)
+
+	for contextName, context := range rawConf.Contexts {
+		userName := context.AuthInfo
+		clusterName := context.Cluster
+		authInfo, userFound := rawConf.AuthInfos[userName]
+		cluster, clusterFound := rawConf.Clusters[clusterName]
+
+		// make sure the cluster is "allowed"
+		_, isAllowed := aContextMap[contextName]
+
+		if userFound && clusterFound && isAllowed {
+			copyConf.Clusters[clusterName] = cluster
+			copyConf.AuthInfos[userName] = authInfo
+			copyConf.Contexts[contextName] = context
+		}
+	}
+
+	// validate the copyConf and create a ClientConfig
+	err := clientcmd.Validate(*copyConf)
+
+	if err != nil {
+		return nil, err
+	}
+
+	clientConf := clientcmd.NewDefaultClientConfig(*copyConf, &clientcmd.ConfigOverrides{})
+
+	return clientConf, nil
+}
+
+func populateCertificateRefs(config *clientcmdapi.Config) {
+	for _, cluster := range config.Clusters {
+		refs := clientcmd.GetClusterFileReferences(cluster)
+		for _, str := range refs {
+			// only write certificate if the file reference is CA
+			if *str != cluster.CertificateAuthority {
+				break
+			}
+
+			fileBytes, err := ioutil.ReadFile(*str)
+
+			if err != nil {
+				continue
+			}
+
+			cluster.CertificateAuthorityData = fileBytes
+			cluster.CertificateAuthority = ""
+		}
+	}
+
+	for _, authInfo := range config.AuthInfos {
+		refs := clientcmd.GetAuthInfoFileReferences(authInfo)
+		for _, str := range refs {
+			if *str == "" {
+				continue
+			}
+
+			var refType int
+
+			if authInfo.ClientCertificate == *str {
+				refType = 0
+			} else if authInfo.ClientKey == *str {
+				refType = 1
+			} else if authInfo.TokenFile == *str {
+				refType = 2
+			}
+
+			fileBytes, err := ioutil.ReadFile(*str)
+
+			if err != nil {
+				continue
+			}
+
+			if refType == 0 {
+				authInfo.ClientCertificateData = fileBytes
+				authInfo.ClientCertificate = ""
+			} else if refType == 1 {
+				authInfo.ClientKeyData = fileBytes
+				authInfo.ClientKey = ""
+			} else if refType == 2 {
+				authInfo.Token = base64.StdEncoding.EncodeToString(fileBytes)
+				authInfo.TokenFile = ""
+			}
+		}
+	}
+}
+
+func populateOIDCPluginCerts(config *clientcmdapi.Config) {
+	for _, authInfo := range config.AuthInfos {
+		if authInfo.AuthProvider != nil && authInfo.AuthProvider.Name == "oidc" {
+			if ca, ok := authInfo.AuthProvider.Config["idp-certificate-authority"]; ok && ca != "" {
+				fileBytes, err := ioutil.ReadFile(ca)
+
+				if err != nil {
+					continue
+				}
+
+				authInfo.AuthProvider.Config["idp-certificate-authority-data"] = base64.StdEncoding.EncodeToString(fileBytes)
+				delete(authInfo.AuthProvider.Config, "idp-certificate-authority")
+			}
+		}
+	}
+}

+ 0 - 33
package-lock.json

@@ -1,33 +0,0 @@
-{
-  "requires": true,
-  "lockfileVersion": 1,
-  "dependencies": {
-    "argparse": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
-      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
-      "requires": {
-        "sprintf-js": "~1.0.2"
-      }
-    },
-    "esprima": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
-      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
-    },
-    "js-yaml": {
-      "version": "3.14.0",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
-      "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
-      "requires": {
-        "argparse": "^1.0.7",
-        "esprima": "^4.0.0"
-      }
-    },
-    "sprintf-js": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
-      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
-    }
-  }
-}