istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/multicluster/remote_secret.go (about) 1 // Copyright Istio Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package multicluster 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "io" 22 "strings" 23 "time" 24 25 "github.com/cenkalti/backoff/v4" 26 "github.com/spf13/cobra" 27 "github.com/spf13/pflag" 28 v1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/serializer/json" 32 "k8s.io/apimachinery/pkg/runtime/serializer/versioning" 33 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 34 _ "k8s.io/client-go/plugin/pkg/client/auth" // to avoid 'No Auth Provider found for name "gcp"' 35 "k8s.io/client-go/tools/clientcmd" 36 "k8s.io/client-go/tools/clientcmd/api" 37 "k8s.io/client-go/tools/clientcmd/api/latest" 38 39 "istio.io/istio/istioctl/pkg/cli" 40 "istio.io/istio/istioctl/pkg/util" 41 "istio.io/istio/operator/pkg/helm" 42 "istio.io/istio/pkg/config/constants" 43 "istio.io/istio/pkg/config/labels" 44 "istio.io/istio/pkg/kube" 45 "istio.io/istio/pkg/kube/multicluster" 46 "istio.io/istio/pkg/log" 47 ) 48 49 var ( 50 codec runtime.Codec 51 scheme *runtime.Scheme 52 53 tokenWaitBackoff = time.Second 54 ) 55 56 func init() { 57 scheme = runtime.NewScheme() 58 utilruntime.Must(v1.AddToScheme(scheme)) 59 opt := json.SerializerOptions{ 60 Yaml: true, 61 Pretty: false, 62 Strict: false, 63 } 64 yamlSerializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, opt) 65 codec = versioning.NewDefaultingCodecForScheme( 66 scheme, 67 yamlSerializer, 68 yamlSerializer, 69 v1.SchemeGroupVersion, 70 runtime.InternalGroupVersioner, 71 ) 72 } 73 74 const ( 75 remoteSecretPrefix = "istio-remote-secret-" 76 configSecretName = "istio-kubeconfig" 77 configSecretKey = "config" 78 ) 79 80 func remoteSecretNameFromClusterName(clusterName string) string { 81 return remoteSecretPrefix + clusterName 82 } 83 84 // NewCreateRemoteSecretCommand creates a new command for joining two contexts 85 // together in a multi-cluster mesh. 86 func NewCreateRemoteSecretCommand(ctx cli.Context) *cobra.Command { 87 opts := RemoteSecretOptions{ 88 AuthType: RemoteSecretAuthTypeBearerToken, 89 AuthPluginConfig: make(map[string]string), 90 Type: SecretTypeRemote, 91 } 92 c := &cobra.Command{ 93 Use: "create-remote-secret", 94 Short: "Create a secret with credentials to allow Istio to access remote Kubernetes apiservers", 95 Example: ` # Create a secret to access cluster c0's apiserver and install it in cluster c1. 96 istioctl --kubeconfig=c0.yaml create-remote-secret --name c0 \ 97 | kubectl --kubeconfig=c1.yaml apply -f - 98 99 # Delete a secret that was previously installed in c1 100 istioctl --kubeconfig=c0.yaml create-remote-secret --name c0 \ 101 | kubectl --kubeconfig=c1.yaml delete -f - 102 103 # Create a secret access a remote cluster with an auth plugin 104 istioctl --kubeconfig=c0.yaml create-remote-secret --name c0 --auth-type=plugin --auth-plugin-name=gcp \ 105 | kubectl --kubeconfig=c1.yaml apply -f -`, 106 Args: cobra.NoArgs, 107 RunE: func(c *cobra.Command, args []string) error { 108 if err := opts.prepare(ctx); err != nil { 109 return err 110 } 111 client, err := ctx.CLIClient() 112 if err != nil { 113 return err 114 } 115 out, warn, err := CreateRemoteSecret(client, opts) 116 if err != nil { 117 _, _ = fmt.Fprintf(c.OutOrStderr(), "error: %v\n", err) 118 return err 119 } 120 if warn != nil { 121 _, _ = fmt.Fprintf(c.OutOrStderr(), "warn: %v\n", warn) 122 } 123 _, _ = fmt.Fprint(c.OutOrStdout(), out) 124 return nil 125 }, 126 } 127 opts.addFlags(c.PersistentFlags()) 128 return c 129 } 130 131 func createRemoteServiceAccountSecret(kubeconfig *api.Config, clusterName, secName string) (*v1.Secret, error) { // nolint:interfacer 132 var data bytes.Buffer 133 if err := latest.Codec.Encode(kubeconfig, &data); err != nil { 134 return nil, err 135 } 136 key := clusterName 137 if secName == configSecretName { 138 key = configSecretKey 139 } 140 out := &v1.Secret{ 141 ObjectMeta: metav1.ObjectMeta{ 142 Name: secName, 143 Annotations: map[string]string{ 144 clusterNameAnnotationKey: clusterName, 145 }, 146 Labels: map[string]string{ 147 multicluster.MultiClusterSecretLabel: "true", 148 }, 149 }, 150 Data: map[string][]byte{ 151 key: data.Bytes(), 152 }, 153 } 154 return out, nil 155 } 156 157 func createBaseKubeconfig(caData []byte, clusterName, server string) *api.Config { 158 return &api.Config{ 159 Clusters: map[string]*api.Cluster{ 160 clusterName: { 161 CertificateAuthorityData: caData, 162 Server: server, 163 }, 164 }, 165 AuthInfos: map[string]*api.AuthInfo{}, 166 Contexts: map[string]*api.Context{ 167 clusterName: { 168 Cluster: clusterName, 169 AuthInfo: clusterName, 170 }, 171 }, 172 CurrentContext: clusterName, 173 } 174 } 175 176 func createBearerTokenKubeconfig(caData, token []byte, clusterName, server string) *api.Config { 177 c := createBaseKubeconfig(caData, clusterName, server) 178 c.AuthInfos[c.CurrentContext] = &api.AuthInfo{ 179 Token: string(token), 180 } 181 return c 182 } 183 184 func createPluginKubeconfig(caData []byte, clusterName, server string, authProviderConfig *api.AuthProviderConfig) *api.Config { 185 c := createBaseKubeconfig(caData, clusterName, server) 186 c.AuthInfos[c.CurrentContext] = &api.AuthInfo{ 187 AuthProvider: authProviderConfig, 188 } 189 return c 190 } 191 192 func createRemoteSecretFromPlugin( 193 tokenSecret *v1.Secret, 194 server, clusterName, secName string, 195 authProviderConfig *api.AuthProviderConfig, 196 ) (*v1.Secret, error) { 197 caData, ok := tokenSecret.Data[v1.ServiceAccountRootCAKey] 198 if !ok { 199 return nil, errMissingRootCAKey 200 } 201 202 // Create a Kubeconfig to access the remote cluster using the auth provider plugin. 203 kubeconfig := createPluginKubeconfig(caData, clusterName, server, authProviderConfig) 204 if err := clientcmd.Validate(*kubeconfig); err != nil { 205 return nil, fmt.Errorf("invalid kubeconfig: %v", err) 206 } 207 208 // Encode the Kubeconfig in a secret that can be loaded by Istio to dynamically discover and access the remote cluster. 209 return createRemoteServiceAccountSecret(kubeconfig, clusterName, secName) 210 } 211 212 var ( 213 errMissingRootCAKey = fmt.Errorf("no %q data found", v1.ServiceAccountRootCAKey) 214 errMissingTokenKey = fmt.Errorf("no %q data found", v1.ServiceAccountTokenKey) 215 ) 216 217 func createRemoteSecretFromTokenAndServer(client kube.CLIClient, tokenSecret *v1.Secret, clusterName, server, secName string) (*v1.Secret, error) { 218 caData, token, err := waitForTokenData(client, tokenSecret) 219 if err != nil { 220 return nil, err 221 } 222 223 // Create a Kubeconfig to access the remote cluster using the remote service account credentials. 224 kubeconfig := createBearerTokenKubeconfig(caData, token, clusterName, server) 225 if err := clientcmd.Validate(*kubeconfig); err != nil { 226 return nil, fmt.Errorf("invalid kubeconfig: %v", err) 227 } 228 229 // Encode the Kubeconfig in a secret that can be loaded by Istio to dynamically discover and access the remote cluster. 230 return createRemoteServiceAccountSecret(kubeconfig, clusterName, secName) 231 } 232 233 func waitForTokenData(client kube.CLIClient, secret *v1.Secret) (ca, token []byte, err error) { 234 ca, token, err = tokenDataFromSecret(secret) 235 if err == nil { 236 return 237 } 238 239 log.Infof("Waiting for data to be populated in %s", secret.Name) 240 err = backoff.Retry( 241 func() error { 242 secret, err = client.Kube().CoreV1().Secrets(secret.Namespace).Get(context.TODO(), secret.Name, metav1.GetOptions{}) 243 if err != nil { 244 return err 245 } 246 ca, token, err = tokenDataFromSecret(secret) 247 return err 248 }, 249 backoff.WithMaxRetries(backoff.NewConstantBackOff(tokenWaitBackoff), 5)) 250 return 251 } 252 253 func tokenDataFromSecret(tokenSecret *v1.Secret) (ca, token []byte, err error) { 254 var ok bool 255 ca, ok = tokenSecret.Data[v1.ServiceAccountRootCAKey] 256 if !ok { 257 err = errMissingRootCAKey 258 return 259 } 260 token, ok = tokenSecret.Data[v1.ServiceAccountTokenKey] 261 if !ok { 262 err = errMissingTokenKey 263 return 264 } 265 return 266 } 267 268 func getServiceAccountSecret(client kube.CLIClient, opt RemoteSecretOptions) (*v1.Secret, error) { 269 // Create the service account if it doesn't exist. 270 serviceAccount, err := getOrCreateServiceAccount(client, opt) 271 if err != nil { 272 return nil, err 273 } 274 275 if !kube.IsAtLeastVersion(client, 24) { 276 return legacyGetServiceAccountSecret(serviceAccount, client, opt) 277 } 278 return getOrCreateServiceAccountSecret(serviceAccount, client, opt) 279 } 280 281 // In Kubernetes 1.24+ we can't assume the secrets will be referenced in the ServiceAccount or be created automatically. 282 // See https://github.com/istio/istio/issues/38246 283 func getOrCreateServiceAccountSecret( 284 serviceAccount *v1.ServiceAccount, 285 client kube.CLIClient, 286 opt RemoteSecretOptions, 287 ) (*v1.Secret, error) { 288 ctx := context.TODO() 289 290 // manually specified secret, make sure it references the ServiceAccount 291 if opt.SecretName != "" { 292 secret, err := client.Kube().CoreV1().Secrets(opt.Namespace).Get(ctx, opt.SecretName, metav1.GetOptions{}) 293 if err != nil { 294 return nil, fmt.Errorf("could not get specified secret %s/%s: %v", 295 opt.Namespace, opt.SecretName, err) 296 } 297 if err := secretReferencesServiceAccount(serviceAccount, secret); err != nil { 298 return nil, err 299 } 300 return secret, nil 301 } 302 303 // first try to find an existing secret that references the SA 304 // TODO will the SA have any reference to secrets anymore, can we avoid this list? 305 allSecrets, err := client.Kube().CoreV1().Secrets(opt.Namespace).List(ctx, metav1.ListOptions{}) 306 if err != nil { 307 return nil, fmt.Errorf("failed listing secrets in %s: %v", opt.Namespace, err) 308 } 309 for _, item := range allSecrets.Items { 310 secret := item 311 if secretReferencesServiceAccount(serviceAccount, &secret) == nil { 312 return &secret, nil 313 } 314 } 315 316 // finally, create the sa token secret manually 317 // https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-service-account-api-token 318 // TODO ephemeral time-based tokens are preferred; we should re-think this 319 log.Infof("Creating token secret for service account %q", serviceAccount.Name) 320 secretName := tokenSecretName(serviceAccount.Name) 321 return client.Kube().CoreV1().Secrets(opt.Namespace).Create(ctx, &v1.Secret{ 322 ObjectMeta: metav1.ObjectMeta{ 323 Name: secretName, 324 Annotations: map[string]string{v1.ServiceAccountNameKey: serviceAccount.Name}, 325 }, 326 Type: v1.SecretTypeServiceAccountToken, 327 }, metav1.CreateOptions{}) 328 } 329 330 func tokenSecretName(saName string) string { 331 return saName + "-istio-remote-secret-token" 332 } 333 334 func secretReferencesServiceAccount(serviceAccount *v1.ServiceAccount, secret *v1.Secret) error { 335 if secret.Type != v1.SecretTypeServiceAccountToken || 336 secret.Annotations[v1.ServiceAccountNameKey] != serviceAccount.Name { 337 return fmt.Errorf("secret %s/%s does not reference ServiceAccount %s", 338 secret.Namespace, secret.Name, serviceAccount.Name) 339 } 340 return nil 341 } 342 343 func legacyGetServiceAccountSecret( 344 serviceAccount *v1.ServiceAccount, 345 client kube.CLIClient, 346 opt RemoteSecretOptions, 347 ) (*v1.Secret, error) { 348 if len(serviceAccount.Secrets) == 0 { 349 return nil, fmt.Errorf("no secret found in the service account: %s", serviceAccount) 350 } 351 352 secretName := "" 353 secretNamespace := "" 354 if opt.SecretName != "" { 355 found := false 356 for _, secret := range serviceAccount.Secrets { 357 if secret.Name == opt.SecretName { 358 found = true 359 secretName = secret.Name 360 secretNamespace = secret.Namespace 361 break 362 } 363 } 364 if !found { 365 return nil, fmt.Errorf("provided secret does not exist: %s", opt.SecretName) 366 } 367 } else { 368 if len(serviceAccount.Secrets) == 1 { 369 secretName = serviceAccount.Secrets[0].Name 370 secretNamespace = serviceAccount.Secrets[0].Namespace 371 } else { 372 return nil, fmt.Errorf("wrong number of secrets (%v) in serviceaccount %s/%s, please use --secret-name to specify one", 373 len(serviceAccount.Secrets), opt.Namespace, opt.ServiceAccountName) 374 } 375 } 376 377 if secretNamespace == "" { 378 secretNamespace = opt.Namespace 379 } 380 return client.Kube().CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) 381 } 382 383 func getOrCreateServiceAccount(client kube.CLIClient, opt RemoteSecretOptions) (*v1.ServiceAccount, error) { 384 if sa, err := client.Kube().CoreV1().ServiceAccounts(opt.Namespace).Get( 385 context.TODO(), opt.ServiceAccountName, metav1.GetOptions{}); err == nil { 386 return sa, nil 387 } else if !opt.CreateServiceAccount { 388 // User chose not to automatically create the service account. 389 return nil, fmt.Errorf("failed retrieving service account %s.%s required for creating "+ 390 "the remote secret (hint: try installing a minimal Istio profile on the cluster first, "+ 391 "or run with '--create-service-account=true'): %v", 392 opt.ServiceAccountName, 393 opt.Namespace, 394 err) 395 } 396 397 if err := createServiceAccount(client, opt); err != nil { 398 return nil, err 399 } 400 401 // Return the newly created service account. 402 sa, err := client.Kube().CoreV1().ServiceAccounts(opt.Namespace).Get( 403 context.TODO(), opt.ServiceAccountName, metav1.GetOptions{}) 404 if err != nil { 405 return nil, fmt.Errorf("failed retrieving service account %s.%s after creating it: %v", 406 opt.ServiceAccountName, opt.Namespace, err) 407 } 408 return sa, nil 409 } 410 411 func createServiceAccount(client kube.CLIClient, opt RemoteSecretOptions) error { 412 yaml, err := generateServiceAccountYAML(opt) 413 if err != nil { 414 return err 415 } 416 417 // Before we can apply the yaml, we have to ensure the system namespace exists. 418 if err := createNamespaceIfNotExist(client, opt.Namespace); err != nil { 419 return err 420 } 421 422 // Apply the YAML to the cluster. 423 return client.ApplyYAMLContents(opt.Namespace, yaml) 424 } 425 426 func generateServiceAccountYAML(opt RemoteSecretOptions) (string, error) { 427 // Create a renderer for the base installation. 428 baseRenderer := helm.NewHelmRenderer(opt.ManifestsPath, "base", "Base", opt.Namespace, nil) 429 discoveryRenderer := helm.NewHelmRenderer(opt.ManifestsPath, "istio-control/istio-discovery", "Pilot", opt.Namespace, nil) 430 431 baseTemplates := []string{"reader-serviceaccount.yaml"} 432 discoveryTemplates := []string{"clusterrole.yaml", "clusterrolebinding.yaml"} 433 434 if err := baseRenderer.Run(); err != nil { 435 return "", fmt.Errorf("failed running base Helm renderer: %w", err) 436 } 437 if err := discoveryRenderer.Run(); err != nil { 438 return "", fmt.Errorf("failed running base discovery Helm renderer: %w", err) 439 } 440 441 values := fmt.Sprintf(` 442 global: 443 istioNamespace: %s 444 `, opt.Namespace) 445 446 // Render the templates required for the service account and role bindings. 447 baseContent, err := baseRenderer.RenderManifestFiltered(values, func(template string) bool { 448 for _, t := range baseTemplates { 449 if strings.Contains(template, t) { 450 return true 451 } 452 } 453 return false 454 }) 455 if err != nil { 456 return "", fmt.Errorf("failed rendering base manifest: %w", err) 457 } 458 discoveryContent, err := discoveryRenderer.RenderManifestFiltered(values, func(template string) bool { 459 for _, t := range discoveryTemplates { 460 if strings.Contains(template, t) { 461 return true 462 } 463 } 464 return false 465 }) 466 if err != nil { 467 return "", fmt.Errorf("failed rendering discovery manifest: %w", err) 468 } 469 470 aggregateContent := fmt.Sprintf(` 471 %s 472 --- 473 %s 474 `, baseContent, discoveryContent) 475 return aggregateContent, nil 476 } 477 478 func createNamespaceIfNotExist(client kube.Client, ns string) error { 479 if _, err := client.Kube().CoreV1().Namespaces().Get(context.TODO(), ns, metav1.GetOptions{}); err != nil { 480 if _, err := client.Kube().CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ 481 ObjectMeta: metav1.ObjectMeta{ 482 Name: ns, 483 }, 484 }, metav1.CreateOptions{}); err != nil { 485 return fmt.Errorf("failed creating namespace %s: %v", ns, err) 486 } 487 } 488 return nil 489 } 490 491 func getServerFromKubeconfig(client kube.CLIClient) (string, Warning, error) { 492 restCfg := client.RESTConfig() 493 if restCfg == nil { 494 return "", nil, fmt.Errorf("failed getting REST config from client") 495 } 496 server := restCfg.Host 497 if strings.Contains(server, "127.0.0.1") || strings.Contains(server, "localhost") { 498 return server, fmt.Errorf( 499 "server in Kubeconfig is %s. This is likely not reachable from inside the cluster, "+ 500 "if you're using Kubernetes in Docker, pass --server with the container IP for the API Server", 501 server), nil 502 } 503 return server, nil, nil 504 } 505 506 const ( 507 outputHeader = "# This file is autogenerated, do not edit.\n" 508 outputTrailer = "---\n" 509 ) 510 511 func writeEncodedObject(out io.Writer, in runtime.Object) error { 512 if _, err := fmt.Fprint(out, outputHeader); err != nil { 513 return err 514 } 515 if err := codec.Encode(in, out); err != nil { 516 return err 517 } 518 if _, err := fmt.Fprint(out, outputTrailer); err != nil { 519 return err 520 } 521 return nil 522 } 523 524 type writer interface { 525 io.Writer 526 String() string 527 } 528 529 func makeOutputWriter() writer { 530 return &bytes.Buffer{} 531 } 532 533 var makeOutputWriterTestHook = makeOutputWriter 534 535 // RemoteSecretAuthType is a strongly typed authentication type suitable for use with pflags.Var(). 536 type ( 537 RemoteSecretAuthType string 538 SecretType string 539 ) 540 541 var _ pflag.Value = (*RemoteSecretAuthType)(nil) 542 543 func (at *RemoteSecretAuthType) String() string { return string(*at) } 544 func (at *RemoteSecretAuthType) Type() string { return "RemoteSecretAuthType" } 545 func (at *RemoteSecretAuthType) Set(in string) error { 546 *at = RemoteSecretAuthType(in) 547 return nil 548 } 549 550 func (at *SecretType) String() string { return string(*at) } 551 func (at *SecretType) Type() string { return "SecretType" } 552 func (at *SecretType) Set(in string) error { 553 *at = SecretType(in) 554 return nil 555 } 556 557 const ( 558 // Use a bearer token for authentication to the remote kubernetes cluster. 559 RemoteSecretAuthTypeBearerToken RemoteSecretAuthType = "bearer-token" 560 561 // Use a custom authentication plugin for the remote kubernetes cluster. 562 RemoteSecretAuthTypePlugin RemoteSecretAuthType = "plugin" 563 564 // Secret generated from remote cluster 565 SecretTypeRemote SecretType = "remote" 566 567 // Secret generated from config cluster 568 SecretTypeConfig SecretType = "config" 569 ) 570 571 // RemoteSecretOptions contains the options for creating a remote secret. 572 type RemoteSecretOptions struct { 573 KubeOptions 574 575 // Name of the local cluster whose credentials are stored in the secret. Must be 576 // DNS1123 label as it will be used for the k8s secret name. 577 ClusterName string 578 579 // Create a secret with this service account's credentials. 580 ServiceAccountName string 581 582 // CreateServiceAccount if true, the service account specified by ServiceAccountName 583 // will be created if it doesn't exist. 584 CreateServiceAccount bool 585 586 // Authentication method for the remote Kubernetes cluster. 587 AuthType RemoteSecretAuthType 588 // Authenticator plugin configuration 589 AuthPluginName string 590 AuthPluginConfig map[string]string 591 592 // Type of the generated secret 593 Type SecretType 594 595 // ManifestsPath is a path to a manifestsPath and profiles directory in the local filesystem, 596 // or URL with a release tgz. This is only used when no reader service account exists and has 597 // to be created. 598 ManifestsPath string 599 600 // ServerOverride overrides the server IP/hostname field from the Kubeconfig 601 ServerOverride string 602 603 // SecretName selects a specific secret from the remote service account, if there are multiple 604 SecretName string 605 } 606 607 func (o *RemoteSecretOptions) addFlags(flagset *pflag.FlagSet) { 608 flagset.StringVar(&o.ServiceAccountName, "service-account", "", 609 "Create a secret with this service account's credentials. Default value is \""+ 610 constants.DefaultServiceAccountName+"\" if --type is \"remote\", \""+ 611 constants.DefaultConfigServiceAccountName+"\" if --type is \"config\".") 612 flagset.BoolVar(&o.CreateServiceAccount, "create-service-account", true, 613 "If true, the service account needed for creating the remote secret will be created "+ 614 "if it doesn't exist.") 615 flagset.StringVar(&o.ClusterName, "name", "", 616 "Name of the local cluster whose credentials are stored "+ 617 "in the secret. If a name is not specified the kube-system namespace's UUID of "+ 618 "the local cluster will be used.") 619 flagset.StringVar(&o.ServerOverride, "server", "", 620 "The address and port of the Kubernetes API server.") 621 flagset.StringVar(&o.SecretName, "secret-name", "", 622 "The name of the specific secret to use from the service-account. Needed when there are multiple secrets in the service account.") 623 var supportedAuthType []string 624 for _, at := range []RemoteSecretAuthType{RemoteSecretAuthTypeBearerToken, RemoteSecretAuthTypePlugin} { 625 supportedAuthType = append(supportedAuthType, string(at)) 626 } 627 var supportedSecretType []string 628 for _, at := range []SecretType{SecretTypeRemote, SecretTypeConfig} { 629 supportedSecretType = append(supportedSecretType, string(at)) 630 } 631 632 flagset.Var(&o.AuthType, "auth-type", 633 fmt.Sprintf("Type of authentication to use. supported values = %v", supportedAuthType)) 634 flagset.StringVar(&o.AuthPluginName, "auth-plugin-name", o.AuthPluginName, 635 fmt.Sprintf("Authenticator plug-in name. --auth-type=%v must be set with this option", 636 RemoteSecretAuthTypePlugin)) 637 flagset.StringToString("auth-plugin-config", o.AuthPluginConfig, 638 fmt.Sprintf("Authenticator plug-in configuration. --auth-type=%v must be set with this option", 639 RemoteSecretAuthTypePlugin)) 640 flagset.Var(&o.Type, "type", 641 fmt.Sprintf("Type of the generated secret. supported values = %v", supportedSecretType)) 642 flagset.StringVarP(&o.ManifestsPath, "manifests", "d", "", util.ManifestsFlagHelpStr) 643 } 644 645 func (o *RemoteSecretOptions) prepare(ctx cli.Context) error { 646 o.KubeOptions.prepare(ctx) 647 648 if o.ClusterName != "" { 649 if !labels.IsDNS1123Label(o.ClusterName) { 650 return fmt.Errorf("%v is not a valid DNS 1123 label", o.ClusterName) 651 } 652 } 653 return nil 654 } 655 656 type Warning error 657 658 func createRemoteSecret(opt RemoteSecretOptions, client kube.CLIClient) (*v1.Secret, Warning, error) { 659 // generate the clusterName if not specified 660 if opt.ClusterName == "" { 661 uid, err := clusterUID(client.Kube()) 662 if err != nil { 663 return nil, nil, err 664 } 665 opt.ClusterName = string(uid) 666 } 667 668 var secretName string 669 switch opt.Type { 670 case SecretTypeRemote: 671 secretName = remoteSecretNameFromClusterName(opt.ClusterName) 672 if opt.ServiceAccountName == "" { 673 opt.ServiceAccountName = constants.DefaultServiceAccountName 674 } 675 case SecretTypeConfig: 676 secretName = configSecretName 677 if opt.ServiceAccountName == "" { 678 opt.ServiceAccountName = constants.DefaultConfigServiceAccountName 679 } 680 default: 681 return nil, nil, fmt.Errorf("unsupported type: %v", opt.Type) 682 } 683 tokenSecret, err := getServiceAccountSecret(client, opt) 684 if err != nil { 685 return nil, nil, fmt.Errorf("could not get access token to read resources from local kube-apiserver: %v", err) 686 } 687 688 var server string 689 var warn Warning 690 if opt.ServerOverride != "" { 691 server = opt.ServerOverride 692 } else { 693 server, warn, err = getServerFromKubeconfig(client) 694 if err != nil { 695 return nil, warn, err 696 } 697 } 698 699 var remoteSecret *v1.Secret 700 switch opt.AuthType { 701 case RemoteSecretAuthTypeBearerToken: 702 remoteSecret, err = createRemoteSecretFromTokenAndServer(client, tokenSecret, opt.ClusterName, server, secretName) 703 case RemoteSecretAuthTypePlugin: 704 authProviderConfig := &api.AuthProviderConfig{ 705 Name: opt.AuthPluginName, 706 Config: opt.AuthPluginConfig, 707 } 708 remoteSecret, err = createRemoteSecretFromPlugin(tokenSecret, server, opt.ClusterName, secretName, 709 authProviderConfig) 710 default: 711 err = fmt.Errorf("unsupported authentication type: %v", opt.AuthType) 712 } 713 if err != nil { 714 return nil, warn, err 715 } 716 717 remoteSecret.Namespace = opt.Namespace 718 return remoteSecret, warn, nil 719 } 720 721 // CreateRemoteSecret creates a remote secret with credentials of the specified service account. 722 // This is useful for providing a cluster access to a remote apiserver. 723 func CreateRemoteSecret(client kube.CLIClient, opt RemoteSecretOptions) (string, Warning, error) { 724 remoteSecret, warn, err := createRemoteSecret(opt, client) 725 if err != nil { 726 return "", warn, err 727 } 728 729 // convert any binary data to the string equivalent for easier review. The 730 // kube-apiserver will convert this to binary before it persists it to storage. 731 remoteSecret.StringData = make(map[string]string, len(remoteSecret.Data)) 732 for k, v := range remoteSecret.Data { 733 remoteSecret.StringData[k] = string(v) 734 } 735 remoteSecret.Data = nil 736 737 w := makeOutputWriterTestHook() 738 if err := writeEncodedObject(w, remoteSecret); err != nil { 739 return "", warn, err 740 } 741 return w.String(), warn, nil 742 }