| // Copyright Istio 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 multicluster |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "io" |
| "os" |
| "strings" |
| "time" |
| ) |
| |
| import ( |
| "github.com/cenkalti/backoff/v4" |
| "github.com/spf13/cobra" |
| "github.com/spf13/pflag" |
| "istio.io/pkg/log" |
| v1 "k8s.io/api/core/v1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/runtime/serializer/json" |
| "k8s.io/apimachinery/pkg/runtime/serializer/versioning" |
| utilruntime "k8s.io/apimachinery/pkg/util/runtime" |
| _ "k8s.io/client-go/plugin/pkg/client/auth" |
| "k8s.io/client-go/tools/clientcmd" |
| "k8s.io/client-go/tools/clientcmd/api" |
| "k8s.io/client-go/tools/clientcmd/api/latest" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/operator/cmd/mesh" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/helm" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/constants" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/labels" |
| "github.com/apache/dubbo-go-pixiu/pkg/kube" |
| "github.com/apache/dubbo-go-pixiu/pkg/kube/multicluster" |
| ) |
| |
| var ( |
| codec runtime.Codec |
| scheme *runtime.Scheme |
| |
| tokenWaitBackoff = time.Second |
| ) |
| |
| func init() { |
| scheme = runtime.NewScheme() |
| utilruntime.Must(v1.AddToScheme(scheme)) |
| opt := json.SerializerOptions{ |
| Yaml: true, |
| Pretty: false, |
| Strict: false, |
| } |
| yamlSerializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, opt) |
| codec = versioning.NewDefaultingCodecForScheme( |
| scheme, |
| yamlSerializer, |
| yamlSerializer, |
| v1.SchemeGroupVersion, |
| runtime.InternalGroupVersioner, |
| ) |
| } |
| |
| const ( |
| remoteSecretPrefix = "istio-remote-secret-" |
| configSecretName = "istio-kubeconfig" |
| configSecretKey = "config" |
| ) |
| |
| func remoteSecretNameFromClusterName(clusterName string) string { |
| return remoteSecretPrefix + clusterName |
| } |
| |
| // NewCreateRemoteSecretCommand creates a new command for joining two contexts |
| // together in a multi-cluster mesh. |
| func NewCreateRemoteSecretCommand() *cobra.Command { |
| opts := RemoteSecretOptions{ |
| AuthType: RemoteSecretAuthTypeBearerToken, |
| AuthPluginConfig: make(map[string]string), |
| Type: SecretTypeRemote, |
| } |
| c := &cobra.Command{ |
| Use: "create-remote-secret", |
| Short: "Create a secret with credentials to allow Istio to access remote Kubernetes apiservers", |
| Example: ` # Create a secret to access cluster c0's apiserver and install it in cluster c1. |
| istioctl --kubeconfig=c0.yaml x create-remote-secret --name c0 \ |
| | kubectl --kubeconfig=c1.yaml apply -f - |
| |
| # Delete a secret that was previously installed in c1 |
| istioctl --kubeconfig=c0.yaml x create-remote-secret --name c0 \ |
| | kubectl --kubeconfig=c1.yaml delete -f - |
| |
| # Create a secret access a remote cluster with an auth plugin |
| istioctl --kubeconfig=c0.yaml x create-remote-secret --name c0 --auth-type=plugin --auth-plugin-name=gcp \ |
| | kubectl --kubeconfig=c1.yaml apply -f -`, |
| Args: cobra.NoArgs, |
| RunE: func(c *cobra.Command, args []string) error { |
| if err := opts.prepare(c.Flags()); err != nil { |
| return err |
| } |
| env, err := NewEnvironmentFromCobra(opts.Kubeconfig, opts.Context, c) |
| if err != nil { |
| return err |
| } |
| out, warn, err := CreateRemoteSecret(opts, env) |
| if err != nil { |
| _, _ = fmt.Fprintf(c.OutOrStderr(), "error: %v\n", err) |
| return err |
| } |
| if warn != nil { |
| _, _ = fmt.Fprintf(c.OutOrStderr(), "warn: %v\n", warn) |
| } |
| _, _ = fmt.Fprint(c.OutOrStdout(), out) |
| return nil |
| }, |
| } |
| opts.addFlags(c.PersistentFlags()) |
| return c |
| } |
| |
| func createRemoteServiceAccountSecret(kubeconfig *api.Config, clusterName, secName string) (*v1.Secret, error) { // nolint:interfacer |
| var data bytes.Buffer |
| if err := latest.Codec.Encode(kubeconfig, &data); err != nil { |
| return nil, err |
| } |
| key := clusterName |
| if secName == configSecretName { |
| key = configSecretKey |
| } |
| out := &v1.Secret{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: secName, |
| Annotations: map[string]string{ |
| clusterNameAnnotationKey: clusterName, |
| }, |
| Labels: map[string]string{ |
| multicluster.MultiClusterSecretLabel: "true", |
| }, |
| }, |
| Data: map[string][]byte{ |
| key: data.Bytes(), |
| }, |
| } |
| return out, nil |
| } |
| |
| func createBaseKubeconfig(caData []byte, clusterName, server string) *api.Config { |
| return &api.Config{ |
| Clusters: map[string]*api.Cluster{ |
| clusterName: { |
| CertificateAuthorityData: caData, |
| Server: server, |
| }, |
| }, |
| AuthInfos: map[string]*api.AuthInfo{}, |
| Contexts: map[string]*api.Context{ |
| clusterName: { |
| Cluster: clusterName, |
| AuthInfo: clusterName, |
| }, |
| }, |
| CurrentContext: clusterName, |
| } |
| } |
| |
| func createBearerTokenKubeconfig(caData, token []byte, clusterName, server string) *api.Config { |
| c := createBaseKubeconfig(caData, clusterName, server) |
| c.AuthInfos[c.CurrentContext] = &api.AuthInfo{ |
| Token: string(token), |
| } |
| return c |
| } |
| |
| func createPluginKubeconfig(caData []byte, clusterName, server string, authProviderConfig *api.AuthProviderConfig) *api.Config { |
| c := createBaseKubeconfig(caData, clusterName, server) |
| c.AuthInfos[c.CurrentContext] = &api.AuthInfo{ |
| AuthProvider: authProviderConfig, |
| } |
| return c |
| } |
| |
| func createRemoteSecretFromPlugin( |
| tokenSecret *v1.Secret, |
| server, clusterName, secName string, |
| authProviderConfig *api.AuthProviderConfig, |
| ) (*v1.Secret, error) { |
| caData, ok := tokenSecret.Data[v1.ServiceAccountRootCAKey] |
| if !ok { |
| return nil, errMissingRootCAKey |
| } |
| |
| // Create a Kubeconfig to access the remote cluster using the auth provider plugin. |
| kubeconfig := createPluginKubeconfig(caData, clusterName, server, authProviderConfig) |
| if err := clientcmd.Validate(*kubeconfig); err != nil { |
| return nil, fmt.Errorf("invalid kubeconfig: %v", err) |
| } |
| |
| // Encode the Kubeconfig in a secret that can be loaded by Istio to dynamically discover and access the remote cluster. |
| return createRemoteServiceAccountSecret(kubeconfig, clusterName, secName) |
| } |
| |
| var ( |
| errMissingRootCAKey = fmt.Errorf("no %q data found", v1.ServiceAccountRootCAKey) |
| errMissingTokenKey = fmt.Errorf("no %q data found", v1.ServiceAccountTokenKey) |
| ) |
| |
| func createRemoteSecretFromTokenAndServer(client kube.ExtendedClient, tokenSecret *v1.Secret, clusterName, server, secName string) (*v1.Secret, error) { |
| caData, token, err := waitForTokenData(client, tokenSecret) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Create a Kubeconfig to access the remote cluster using the remote service account credentials. |
| kubeconfig := createBearerTokenKubeconfig(caData, token, clusterName, server) |
| if err := clientcmd.Validate(*kubeconfig); err != nil { |
| return nil, fmt.Errorf("invalid kubeconfig: %v", err) |
| } |
| |
| // Encode the Kubeconfig in a secret that can be loaded by Istio to dynamically discover and access the remote cluster. |
| return createRemoteServiceAccountSecret(kubeconfig, clusterName, secName) |
| } |
| |
| func waitForTokenData(client kube.ExtendedClient, secret *v1.Secret) (ca, token []byte, err error) { |
| ca, token, err = tokenDataFromSecret(secret) |
| if err == nil { |
| return |
| } |
| |
| log.Infof("Waiting for data to be populated in %s", secret.Name) |
| err = backoff.Retry( |
| func() error { |
| secret, err = client.Kube().CoreV1().Secrets(secret.Namespace).Get(context.TODO(), secret.Name, metav1.GetOptions{}) |
| if err != nil { |
| return err |
| } |
| ca, token, err = tokenDataFromSecret(secret) |
| return err |
| }, |
| backoff.WithMaxRetries(backoff.NewConstantBackOff(tokenWaitBackoff), 5)) |
| return |
| } |
| |
| func tokenDataFromSecret(tokenSecret *v1.Secret) (ca, token []byte, err error) { |
| var ok bool |
| ca, ok = tokenSecret.Data[v1.ServiceAccountRootCAKey] |
| if !ok { |
| err = errMissingRootCAKey |
| return |
| } |
| token, ok = tokenSecret.Data[v1.ServiceAccountTokenKey] |
| if !ok { |
| err = errMissingTokenKey |
| return |
| } |
| return |
| } |
| |
| func getServiceAccountSecret(client kube.ExtendedClient, opt RemoteSecretOptions) (*v1.Secret, error) { |
| // Create the service account if it doesn't exist. |
| serviceAccount, err := getOrCreateServiceAccount(client, opt) |
| if err != nil { |
| return nil, err |
| } |
| |
| if !kube.IsAtLeastVersion(client, 24) { |
| return legacyGetServiceAccountSecret(serviceAccount, client, opt) |
| } |
| return getOrCreateServiceAccountSecret(serviceAccount, client, opt) |
| } |
| |
| // In Kubernetes 1.24+ we can't assume the secrets will be referenced in the ServiceAccount or be created automatically. |
| // See https://github.com/istio/istio/issues/38246 |
| func getOrCreateServiceAccountSecret( |
| serviceAccount *v1.ServiceAccount, |
| client kube.ExtendedClient, |
| opt RemoteSecretOptions, |
| ) (*v1.Secret, error) { |
| ctx := context.TODO() |
| |
| // manually specified secret, make sure it references the ServiceAccount |
| if opt.SecretName != "" { |
| secret, err := client.Kube().CoreV1().Secrets(opt.Namespace).Get(ctx, opt.SecretName, metav1.GetOptions{}) |
| if err != nil { |
| return nil, fmt.Errorf("could not get specified secret %s/%s: %v", |
| opt.Namespace, opt.SecretName, err) |
| } |
| if err := secretReferencesServiceAccount(serviceAccount, secret); err != nil { |
| return nil, err |
| } |
| return secret, nil |
| } |
| |
| // first try to find an existing secret that references the SA |
| // TODO will the SA have any reference to secrets anymore, can we avoid this list? |
| allSecrets, err := client.Kube().CoreV1().Secrets(opt.Namespace).List(ctx, metav1.ListOptions{}) |
| if err != nil { |
| return nil, fmt.Errorf("failed listing secrets in %s: %v", opt.Namespace, err) |
| } |
| for _, item := range allSecrets.Items { |
| secret := item |
| if secretReferencesServiceAccount(serviceAccount, &secret) == nil { |
| return &secret, nil |
| } |
| } |
| |
| // finally, create the sa token secret manually |
| // https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-service-account-api-token |
| // TODO ephemeral time-based tokens are preferred; we should re-think this |
| log.Infof("Creating token secret for service account %q", serviceAccount.Name) |
| secretName := tokenSecretName(serviceAccount.Name) |
| return client.Kube().CoreV1().Secrets(opt.Namespace).Create(ctx, &v1.Secret{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: secretName, |
| Annotations: map[string]string{v1.ServiceAccountNameKey: serviceAccount.Name}, |
| }, |
| Type: v1.SecretTypeServiceAccountToken, |
| }, metav1.CreateOptions{}) |
| } |
| |
| func tokenSecretName(saName string) string { |
| return saName + "-istio-remote-secret-token" |
| } |
| |
| func secretReferencesServiceAccount(serviceAccount *v1.ServiceAccount, secret *v1.Secret) error { |
| if secret.Type != v1.SecretTypeServiceAccountToken || |
| secret.Annotations[v1.ServiceAccountNameKey] != serviceAccount.Name { |
| return fmt.Errorf("secret %s/%s does not reference ServiceAccount %s", |
| secret.Namespace, secret.Name, serviceAccount.Name) |
| } |
| return nil |
| } |
| |
| func legacyGetServiceAccountSecret( |
| serviceAccount *v1.ServiceAccount, |
| client kube.ExtendedClient, |
| opt RemoteSecretOptions, |
| ) (*v1.Secret, error) { |
| if len(serviceAccount.Secrets) == 0 { |
| return nil, fmt.Errorf("no secret found in the service account: %s", serviceAccount) |
| } |
| |
| secretName := "" |
| secretNamespace := "" |
| if opt.SecretName != "" { |
| found := false |
| for _, secret := range serviceAccount.Secrets { |
| if secret.Name == opt.SecretName { |
| found = true |
| secretName = secret.Name |
| secretNamespace = secret.Namespace |
| break |
| } |
| } |
| if !found { |
| return nil, fmt.Errorf("provided secret does not exist: %s", opt.SecretName) |
| } |
| } else { |
| if len(serviceAccount.Secrets) == 1 { |
| secretName = serviceAccount.Secrets[0].Name |
| secretNamespace = serviceAccount.Secrets[0].Namespace |
| } else { |
| return nil, fmt.Errorf("wrong number of secrets (%v) in serviceaccount %s/%s, please use --secret-name to specify one", |
| len(serviceAccount.Secrets), opt.Namespace, opt.ServiceAccountName) |
| } |
| } |
| |
| if secretNamespace == "" { |
| secretNamespace = opt.Namespace |
| } |
| return client.Kube().CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) |
| } |
| |
| func getOrCreateServiceAccount(client kube.ExtendedClient, opt RemoteSecretOptions) (*v1.ServiceAccount, error) { |
| if sa, err := client.Kube().CoreV1().ServiceAccounts(opt.Namespace).Get( |
| context.TODO(), opt.ServiceAccountName, metav1.GetOptions{}); err == nil { |
| return sa, nil |
| } else if !opt.CreateServiceAccount { |
| // User chose not to automatically create the service account. |
| return nil, fmt.Errorf("failed retrieving service account %s.%s required for creating "+ |
| "the remote secret (hint: try installing a minimal Istio profile on the cluster first, "+ |
| "or run with '--create-service-account=true'): %v", |
| opt.ServiceAccountName, |
| opt.Namespace, |
| err) |
| } |
| |
| if err := createServiceAccount(client, opt); err != nil { |
| return nil, err |
| } |
| |
| // Return the newly created service account. |
| sa, err := client.Kube().CoreV1().ServiceAccounts(opt.Namespace).Get( |
| context.TODO(), opt.ServiceAccountName, metav1.GetOptions{}) |
| if err != nil { |
| return nil, fmt.Errorf("failed retrieving service account %s.%s after creating it: %v", |
| opt.ServiceAccountName, opt.Namespace, err) |
| } |
| return sa, nil |
| } |
| |
| func createServiceAccount(client kube.ExtendedClient, opt RemoteSecretOptions) error { |
| yaml, err := generateServiceAccountYAML(opt) |
| if err != nil { |
| return err |
| } |
| |
| // Before we can apply the yaml, we have to ensure the system namespace exists. |
| if err := createNamespaceIfNotExist(client, opt.Namespace); err != nil { |
| return err |
| } |
| |
| // Apply the YAML to the cluster. |
| return applyYAML(client, yaml, opt.Namespace) |
| } |
| |
| func generateServiceAccountYAML(opt RemoteSecretOptions) (string, error) { |
| // Create a renderer for the base installation. |
| baseRenderer := helm.NewHelmRenderer(opt.ManifestsPath, "base", "Base", opt.Namespace, nil) |
| discoveryRenderer := helm.NewHelmRenderer(opt.ManifestsPath, "istio-control/istio-discovery", "Pilot", opt.Namespace, nil) |
| |
| baseTemplates := []string{"reader-serviceaccount.yaml"} |
| discoveryTemplates := []string{"clusterrole.yaml", "clusterrolebinding.yaml"} |
| |
| if err := baseRenderer.Run(); err != nil { |
| return "", fmt.Errorf("failed running base Helm renderer: %w", err) |
| } |
| if err := discoveryRenderer.Run(); err != nil { |
| return "", fmt.Errorf("failed running base discovery Helm renderer: %w", err) |
| } |
| |
| values := fmt.Sprintf(` |
| global: |
| istioNamespace: %s |
| `, opt.Namespace) |
| |
| // Render the templates required for the service account and role bindings. |
| baseContent, err := baseRenderer.RenderManifestFiltered(values, func(template string) bool { |
| for _, t := range baseTemplates { |
| if strings.Contains(template, t) { |
| return true |
| } |
| } |
| return false |
| }) |
| if err != nil { |
| return "", fmt.Errorf("failed rendering base manifest: %w", err) |
| } |
| discoveryContent, err := discoveryRenderer.RenderManifestFiltered(values, func(template string) bool { |
| for _, t := range discoveryTemplates { |
| if strings.Contains(template, t) { |
| return true |
| } |
| } |
| return false |
| }) |
| if err != nil { |
| return "", fmt.Errorf("failed rendering discovery manifest: %w", err) |
| } |
| |
| aggregateContent := fmt.Sprintf(` |
| %s |
| --- |
| %s |
| `, baseContent, discoveryContent) |
| return aggregateContent, nil |
| } |
| |
| func applyYAML(client kube.ExtendedClient, yamlContent, ns string) error { |
| yamlFile, err := writeToTempFile(yamlContent) |
| if err != nil { |
| return fmt.Errorf("failed creating manifest file: %v", err) |
| } |
| |
| // Apply the YAML to the cluster. |
| if err := client.ApplyYAMLFiles(ns, yamlFile); err != nil { |
| return fmt.Errorf("failed applying manifest %s: %v", yamlFile, err) |
| } |
| return nil |
| } |
| |
| func createNamespaceIfNotExist(client kube.Client, ns string) error { |
| if _, err := client.Kube().CoreV1().Namespaces().Get(context.TODO(), ns, metav1.GetOptions{}); err != nil { |
| if _, err := client.Kube().CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: ns, |
| }, |
| }, metav1.CreateOptions{}); err != nil { |
| return fmt.Errorf("failed creating namespace %s: %v", ns, err) |
| } |
| } |
| return nil |
| } |
| |
| func writeToTempFile(content string) (string, error) { |
| outFile, err := os.CreateTemp("", "remote-secret-manifest-*") |
| if err != nil { |
| return "", fmt.Errorf("failed creating temp file for manifest: %v", err) |
| } |
| defer func() { _ = outFile.Close() }() |
| |
| if _, err := outFile.Write([]byte(content)); err != nil { |
| return "", fmt.Errorf("failed writing manifest file: %v", err) |
| } |
| return outFile.Name(), nil |
| } |
| |
| func getServerFromKubeconfig(context string, config *api.Config) (string, Warning, error) { |
| if context == "" { |
| context = config.CurrentContext |
| } |
| |
| configContext, ok := config.Contexts[context] |
| if !ok { |
| return "", nil, fmt.Errorf("could not find cluster for context %q", context) |
| } |
| cluster, ok := config.Clusters[configContext.Cluster] |
| if !ok { |
| return "", nil, fmt.Errorf("could not find server for context %q", context) |
| } |
| if strings.Contains(cluster.Server, "127.0.0.1") || strings.Contains(cluster.Server, "localhost") { |
| return cluster.Server, fmt.Errorf( |
| "server in Kubeconfig is %s. This is likely not reachable from inside the cluster, "+ |
| "if you're using Kubernetes in Docker, pass --server with the container IP for the API Server", |
| cluster.Server), nil |
| } |
| return cluster.Server, nil, nil |
| } |
| |
| const ( |
| outputHeader = "# This file is autogenerated, do not edit.\n" |
| outputTrailer = "---\n" |
| ) |
| |
| func writeEncodedObject(out io.Writer, in runtime.Object) error { |
| if _, err := fmt.Fprint(out, outputHeader); err != nil { |
| return err |
| } |
| if err := codec.Encode(in, out); err != nil { |
| return err |
| } |
| if _, err := fmt.Fprint(out, outputTrailer); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| type writer interface { |
| io.Writer |
| String() string |
| } |
| |
| func makeOutputWriter() writer { |
| return &bytes.Buffer{} |
| } |
| |
| var makeOutputWriterTestHook = makeOutputWriter |
| |
| // RemoteSecretAuthType is a strongly typed authentication type suitable for use with pflags.Var(). |
| type ( |
| RemoteSecretAuthType string |
| SecretType string |
| ) |
| |
| var _ pflag.Value = (*RemoteSecretAuthType)(nil) |
| |
| func (at *RemoteSecretAuthType) String() string { return string(*at) } |
| func (at *RemoteSecretAuthType) Type() string { return "RemoteSecretAuthType" } |
| func (at *RemoteSecretAuthType) Set(in string) error { |
| *at = RemoteSecretAuthType(in) |
| return nil |
| } |
| |
| func (at *SecretType) String() string { return string(*at) } |
| func (at *SecretType) Type() string { return "SecretType" } |
| func (at *SecretType) Set(in string) error { |
| *at = SecretType(in) |
| return nil |
| } |
| |
| const ( |
| // Use a bearer token for authentication to the remote kubernetes cluster. |
| RemoteSecretAuthTypeBearerToken RemoteSecretAuthType = "bearer-token" |
| |
| // Use a custom authentication plugin for the remote kubernetes cluster. |
| RemoteSecretAuthTypePlugin RemoteSecretAuthType = "plugin" |
| |
| // Secret generated from remote cluster |
| SecretTypeRemote SecretType = "remote" |
| |
| // Secret generated from config cluster |
| SecretTypeConfig SecretType = "config" |
| ) |
| |
| // RemoteSecretOptions contains the options for creating a remote secret. |
| type RemoteSecretOptions struct { |
| KubeOptions |
| |
| // Name of the local cluster whose credentials are stored in the secret. Must be |
| // DNS1123 label as it will be used for the k8s secret name. |
| ClusterName string |
| |
| // Create a secret with this service account's credentials. |
| ServiceAccountName string |
| |
| // CreateServiceAccount if true, the service account specified by ServiceAccountName |
| // will be created if it doesn't exist. |
| CreateServiceAccount bool |
| |
| // Authentication method for the remote Kubernetes cluster. |
| AuthType RemoteSecretAuthType |
| // Authenticator plugin configuration |
| AuthPluginName string |
| AuthPluginConfig map[string]string |
| |
| // Type of the generated secret |
| Type SecretType |
| |
| // ManifestsPath is a path to a manifestsPath and profiles directory in the local filesystem, |
| // or URL with a release tgz. This is only used when no reader service account exists and has |
| // to be created. |
| ManifestsPath string |
| |
| // ServerOverride overrides the server IP/hostname field from the Kubeconfig |
| ServerOverride string |
| |
| // SecretName selects a specific secret from the remote service account, if there are multiple |
| SecretName string |
| } |
| |
| func (o *RemoteSecretOptions) addFlags(flagset *pflag.FlagSet) { |
| flagset.StringVar(&o.ServiceAccountName, "service-account", "", |
| "Create a secret with this service account's credentials. Default value is \""+ |
| constants.DefaultServiceAccountName+"\" if --type is \"remote\", \""+ |
| constants.DefaultConfigServiceAccountName+"\" if --type is \"config\".") |
| flagset.BoolVar(&o.CreateServiceAccount, "create-service-account", true, |
| "If true, the service account needed for creating the remote secret will be created "+ |
| "if it doesn't exist.") |
| flagset.StringVar(&o.ClusterName, "name", "", |
| "Name of the local cluster whose credentials are stored "+ |
| "in the secret. If a name is not specified the kube-system namespace's UUID of "+ |
| "the local cluster will be used.") |
| flagset.StringVar(&o.ServerOverride, "server", "", |
| "The address and port of the Kubernetes API server.") |
| flagset.StringVar(&o.SecretName, "secret-name", "", |
| "The name of the specific secret to use from the service-account. Needed when there are multiple secrets in the service account.") |
| var supportedAuthType []string |
| for _, at := range []RemoteSecretAuthType{RemoteSecretAuthTypeBearerToken, RemoteSecretAuthTypePlugin} { |
| supportedAuthType = append(supportedAuthType, string(at)) |
| } |
| var supportedSecretType []string |
| for _, at := range []SecretType{SecretTypeRemote, SecretTypeConfig} { |
| supportedSecretType = append(supportedSecretType, string(at)) |
| } |
| |
| flagset.Var(&o.AuthType, "auth-type", |
| fmt.Sprintf("Type of authentication to use. supported values = %v", supportedAuthType)) |
| flagset.StringVar(&o.AuthPluginName, "auth-plugin-name", o.AuthPluginName, |
| fmt.Sprintf("Authenticator plug-in name. --auth-type=%v must be set with this option", |
| RemoteSecretAuthTypePlugin)) |
| flagset.StringToString("auth-plugin-config", o.AuthPluginConfig, |
| fmt.Sprintf("Authenticator plug-in configuration. --auth-type=%v must be set with this option", |
| RemoteSecretAuthTypePlugin)) |
| flagset.Var(&o.Type, "type", |
| fmt.Sprintf("Type of the generated secret. supported values = %v", supportedSecretType)) |
| flagset.StringVarP(&o.ManifestsPath, "manifests", "d", "", mesh.ManifestsFlagHelpStr) |
| } |
| |
| func (o *RemoteSecretOptions) prepare(flags *pflag.FlagSet) error { |
| o.KubeOptions.prepare(flags) |
| |
| if o.ClusterName != "" { |
| if !labels.IsDNS1123Label(o.ClusterName) { |
| return fmt.Errorf("%v is not a valid DNS 1123 label", o.ClusterName) |
| } |
| } |
| return nil |
| } |
| |
| type Warning error |
| |
| func createRemoteSecret(opt RemoteSecretOptions, client kube.ExtendedClient, env Environment) (*v1.Secret, Warning, error) { |
| // generate the clusterName if not specified |
| if opt.ClusterName == "" { |
| uid, err := clusterUID(client) |
| if err != nil { |
| return nil, nil, err |
| } |
| opt.ClusterName = string(uid) |
| } |
| |
| var secretName string |
| switch opt.Type { |
| case SecretTypeRemote: |
| secretName = remoteSecretNameFromClusterName(opt.ClusterName) |
| if opt.ServiceAccountName == "" { |
| opt.ServiceAccountName = constants.DefaultServiceAccountName |
| } |
| case SecretTypeConfig: |
| secretName = configSecretName |
| if opt.ServiceAccountName == "" { |
| opt.ServiceAccountName = constants.DefaultConfigServiceAccountName |
| } |
| default: |
| return nil, nil, fmt.Errorf("unsupported type: %v", opt.Type) |
| } |
| tokenSecret, err := getServiceAccountSecret(client, opt) |
| if err != nil { |
| return nil, nil, fmt.Errorf("could not get access token to read resources from local kube-apiserver: %v", err) |
| } |
| |
| var server string |
| var warn Warning |
| if opt.ServerOverride != "" { |
| server = opt.ServerOverride |
| } else { |
| server, warn, err = getServerFromKubeconfig(opt.Context, env.GetConfig()) |
| if err != nil { |
| return nil, warn, err |
| } |
| } |
| |
| var remoteSecret *v1.Secret |
| switch opt.AuthType { |
| case RemoteSecretAuthTypeBearerToken: |
| remoteSecret, err = createRemoteSecretFromTokenAndServer(client, tokenSecret, opt.ClusterName, server, secretName) |
| case RemoteSecretAuthTypePlugin: |
| authProviderConfig := &api.AuthProviderConfig{ |
| Name: opt.AuthPluginName, |
| Config: opt.AuthPluginConfig, |
| } |
| remoteSecret, err = createRemoteSecretFromPlugin(tokenSecret, server, opt.ClusterName, secretName, |
| authProviderConfig) |
| default: |
| err = fmt.Errorf("unsupported authentication type: %v", opt.AuthType) |
| } |
| if err != nil { |
| return nil, warn, err |
| } |
| |
| remoteSecret.Namespace = opt.Namespace |
| return remoteSecret, warn, nil |
| } |
| |
| // CreateRemoteSecret creates a remote secret with credentials of the specified service account. |
| // This is useful for providing a cluster access to a remote apiserver. |
| func CreateRemoteSecret(opt RemoteSecretOptions, env Environment) (string, Warning, error) { |
| client, err := env.CreateClient(opt.Context) |
| if err != nil { |
| return "", nil, err |
| } |
| |
| remoteSecret, warn, err := createRemoteSecret(opt, client, env) |
| if err != nil { |
| return "", warn, err |
| } |
| |
| // convert any binary data to the string equivalent for easier review. The |
| // kube-apiserver will convert this to binary before it persists it to storage. |
| remoteSecret.StringData = make(map[string]string, len(remoteSecret.Data)) |
| for k, v := range remoteSecret.Data { |
| remoteSecret.StringData[k] = string(v) |
| } |
| remoteSecret.Data = nil |
| |
| w := makeOutputWriterTestHook() |
| if err := writeEncodedObject(w, remoteSecret); err != nil { |
| return "", warn, err |
| } |
| return w.String(), warn, nil |
| } |