| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- package kubernetes
- import (
- "encoding/json"
- "errors"
- "net/url"
- "github.com/porter-dev/porter/api/types"
- "github.com/porter-dev/porter/internal/models"
- "k8s.io/client-go/tools/clientcmd"
- "k8s.io/client-go/tools/clientcmd/api"
- )
- // GetClusterCandidatesFromKubeconfig parses a kubeconfig for a list of cluster
- // candidates.
- //
- // The local boolean represents whether the auth mechanism should be designated as
- // "local": if so, the auth mechanism uses local plugins/mechanisms purely from the
- // kubeconfig.
- func GetClusterCandidatesFromKubeconfig(
- kubeconfig []byte,
- projectID uint,
- local bool,
- ) ([]*models.ClusterCandidate, error) {
- config, err := clientcmd.NewClientConfigFromBytes(kubeconfig)
- if err != nil {
- return nil, err
- }
- rawConf, err := config.RawConfig()
- if err != nil {
- return nil, err
- }
- res := make([]*models.ClusterCandidate, 0)
- for contextName, context := range rawConf.Contexts {
- clusterName := context.Cluster
- awsClusterID := ""
- authInfoName := context.AuthInfo
- resolvers := make([]models.ClusterResolver, 0)
- var authMechanism models.ClusterAuth
- if local {
- authMechanism = models.Local
- } else {
- // get the resolvers, if needed
- authMechanism, resolvers = parseAuthInfoForResolvers(rawConf.AuthInfos[authInfoName])
- clusterResolvers := parseClusterForResolvers(rawConf.Clusters[clusterName])
- resolvers = append(resolvers, clusterResolvers...)
- if authMechanism == models.AWS {
- // if the auth mechanism is AWS, we need to parse more explicitly
- // for the cluster id
- awsClusterID = parseAuthInfoForAWSClusterID(rawConf.AuthInfos[authInfoName], clusterName)
- }
- }
- // construct the raw kubeconfig that's relevant for that context
- contextConf, err := getConfigForContext(&rawConf, contextName)
- if err != nil {
- continue
- }
- rawBytes, err := clientcmd.Write(*contextConf)
- if err == nil {
- // create the candidate cluster
- res = append(res, &models.ClusterCandidate{
- AuthMechanism: authMechanism,
- ProjectID: projectID,
- Resolvers: resolvers,
- ContextName: contextName,
- Name: clusterName,
- Server: rawConf.Clusters[clusterName].Server,
- AWSClusterIDGuess: []byte(awsClusterID),
- Kubeconfig: rawBytes,
- })
- }
- }
- return res, nil
- }
- // GetRawConfigFromBytes returns the clientcmdapi.Config from kubeconfig
- // bytes
- func GetRawConfigFromBytes(kubeconfig []byte) (*api.Config, error) {
- config, err := clientcmd.NewClientConfigFromBytes(kubeconfig)
- if err != nil {
- return nil, err
- }
- rawConf, err := config.RawConfig()
- if err != nil {
- return nil, err
- }
- return &rawConf, nil
- }
- // Parsing rules are:
- //
- // (1) If a client certificate + client key exist, uses x509 auth mechanism
- // (2) If an oidc/gcp/aws plugin exists, uses that auth mechanism
- // (3) If a bearer token exists, uses bearer token auth mechanism
- // (4) If a username/password exist, uses basic auth mechanism
- // (5) Otherwise, the config gets skipped
- //
- func parseAuthInfoForResolvers(authInfo *api.AuthInfo) (authMechanism models.ClusterAuth, resolvers []models.ClusterResolver) {
- resolvers = make([]models.ClusterResolver, 0)
- if (authInfo.ClientCertificate != "" || len(authInfo.ClientCertificateData) != 0) &&
- (authInfo.ClientKey != "" || len(authInfo.ClientKeyData) != 0) {
- if len(authInfo.ClientCertificateData) == 0 {
- fn := map[string]string{
- "filename": authInfo.ClientCertificate,
- }
- fnBytes, _ := json.Marshal(&fn)
- resolvers = append(resolvers, models.ClusterResolver{
- Name: types.ClientCertData,
- Resolved: false,
- Data: fnBytes,
- })
- }
- if len(authInfo.ClientKeyData) == 0 {
- fn := map[string]string{
- "filename": authInfo.ClientKey,
- }
- fnBytes, _ := json.Marshal(&fn)
- resolvers = append(resolvers, models.ClusterResolver{
- Name: types.ClientKeyData,
- Resolved: false,
- Data: fnBytes,
- })
- }
- return models.X509, resolvers
- }
- if authInfo.AuthProvider != nil {
- switch authInfo.AuthProvider.Name {
- case "oidc":
- filename, isFile := authInfo.AuthProvider.Config["idp-certificate-authority"]
- data, isData := authInfo.AuthProvider.Config["idp-certificate-authority-data"]
- if isFile && (!isData || data == "") {
- fn := map[string]string{
- "filename": filename,
- }
- fnBytes, _ := json.Marshal(&fn)
- return models.OIDC, []models.ClusterResolver{
- {
- Name: types.OIDCIssuerData,
- Resolved: false,
- Data: fnBytes,
- },
- }
- }
- return models.OIDC, resolvers
- case "gcp":
- return models.GCP, []models.ClusterResolver{
- {
- Name: types.GCPKeyData,
- Resolved: false,
- },
- }
- }
- }
- if authInfo.Exec != nil {
- if authInfo.Exec.Command == "aws" || authInfo.Exec.Command == "aws-iam-authenticator" {
- return models.AWS, []models.ClusterResolver{
- {
- Name: types.AWSData,
- Resolved: false,
- },
- }
- }
- }
- if authInfo.Token != "" || authInfo.TokenFile != "" {
- if authInfo.Token == "" {
- fn := map[string]string{
- "filename": authInfo.TokenFile,
- }
- fnBytes, _ := json.Marshal(&fn)
- return models.Bearer, []models.ClusterResolver{
- {
- Name: types.TokenData,
- Resolved: false,
- Data: fnBytes,
- },
- }
- }
- return models.Bearer, resolvers
- }
- if authInfo.Username != "" && authInfo.Password != "" {
- return models.Basic, resolvers
- }
- return models.X509, resolvers
- }
- // Parses the cluster object to determine resolvers -- only currently supported resolver is
- // population of the cluster certificate authority data
- func parseClusterForResolvers(cluster *api.Cluster) (resolvers []models.ClusterResolver) {
- resolvers = make([]models.ClusterResolver, 0)
- if cluster.CertificateAuthority != "" && len(cluster.CertificateAuthorityData) == 0 {
- fn := map[string]string{
- "filename": cluster.CertificateAuthority,
- }
- fnBytes, _ := json.Marshal(&fn)
- resolvers = append(resolvers, models.ClusterResolver{
- Name: types.ClusterCAData,
- Resolved: false,
- Data: fnBytes,
- })
- }
- serverURL, err := url.Parse(cluster.Server)
- if err == nil {
- if hostname := serverURL.Hostname(); hostname == "127.0.0.1" || hostname == "localhost" {
- resolvers = append(resolvers, models.ClusterResolver{
- Name: types.ClusterLocalhost,
- Resolved: false,
- })
- }
- }
- return resolvers
- }
- func parseAuthInfoForAWSClusterID(authInfo *api.AuthInfo, fallback string) string {
- if authInfo.Exec != nil {
- if authInfo.Exec.Command == "aws" {
- // look for --cluster-name flag
- for i, arg := range authInfo.Exec.Args {
- if arg == "--cluster-name" && len(authInfo.Exec.Args) > i+1 {
- return authInfo.Exec.Args[i+1]
- }
- }
- } else if authInfo.Exec.Command == "aws-iam-authenticator" {
- // look for -i or --cluster-id flag
- for i, arg := range authInfo.Exec.Args {
- if (arg == "-i" || arg == "--cluster-id") && len(authInfo.Exec.Args) > i+1 {
- return authInfo.Exec.Args[i+1]
- }
- }
- }
- }
- return fallback
- }
- // getConfigForContext returns the raw kubeconfig associated with only a
- // single context of the raw config
- func getConfigForContext(
- rawConf *api.Config,
- contextName string,
- ) (*api.Config, error) {
- 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 = contextName
- context, ok := rawConf.Contexts[contextName]
- if ok {
- userName := context.AuthInfo
- clusterName := context.Cluster
- authInfo, userFound := rawConf.AuthInfos[userName]
- cluster, clusterFound := rawConf.Clusters[clusterName]
- if userFound && clusterFound {
- copyConf.Clusters[clusterName] = cluster
- copyConf.AuthInfos[userName] = authInfo
- copyConf.Contexts[contextName] = context
- } else {
- return nil, errors.New("linked user and cluster not found")
- }
- } else {
- return nil, errors.New("context not found")
- }
- return copyConf, nil
- }
- // 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 {
- aContextMap[context] = context
- }
- return aContextMap
- }
|