// createTokenSecret creates a token secret for a given service account. Returns the name of the token func (e *DockercfgController) createTokenSecret(serviceAccount *api.ServiceAccount) (*api.Secret, bool, error) { pendingTokenName := serviceAccount.Annotations[PendingTokenAnnotation] // If this service account has no record of a pending token name, record one if len(pendingTokenName) == 0 { pendingTokenName = secret.Strategy.GenerateName(osautil.GetTokenSecretNamePrefix(serviceAccount)) if serviceAccount.Annotations == nil { serviceAccount.Annotations = map[string]string{} } serviceAccount.Annotations[PendingTokenAnnotation] = pendingTokenName updatedServiceAccount, err := e.client.Core().ServiceAccounts(serviceAccount.Namespace).Update(serviceAccount) // Conflicts mean we'll get called to sync this service account again if kapierrors.IsConflict(err) { return nil, false, nil } if err != nil { return nil, false, err } serviceAccount = updatedServiceAccount } // Return the token from cache existingTokenSecretObj, exists, err := e.secretCache.GetByKey(serviceAccount.Namespace + "/" + pendingTokenName) if err != nil { return nil, false, err } if exists { existingTokenSecret := existingTokenSecretObj.(*api.Secret) return existingTokenSecret, len(existingTokenSecret.Data[api.ServiceAccountTokenKey]) > 0, nil } // Try to create the named pending token tokenSecret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: pendingTokenName, Namespace: serviceAccount.Namespace, Annotations: map[string]string{ api.ServiceAccountNameKey: serviceAccount.Name, api.ServiceAccountUIDKey: string(serviceAccount.UID), api.CreatedByAnnotation: CreateDockercfgSecretsController, }, }, Type: api.SecretTypeServiceAccountToken, Data: map[string][]byte{}, } glog.V(4).Infof("Creating token secret %q for service account %s/%s", tokenSecret.Name, serviceAccount.Namespace, serviceAccount.Name) token, err := e.client.Core().Secrets(tokenSecret.Namespace).Create(tokenSecret) // Already exists but not in cache means we'll get an add watch event and resync if kapierrors.IsAlreadyExists(err) { return nil, false, nil } if err != nil { return nil, false, err } return token, len(token.Data[api.ServiceAccountTokenKey]) > 0, nil }
// createTokenSecret creates a token secret for a given service account. Returns the name of the token func (e *DockercfgController) createTokenSecret(serviceAccount *api.ServiceAccount) (*api.Secret, error) { tokenSecret := &api.Secret{ ObjectMeta: api.ObjectMeta{ Name: secret.Strategy.GenerateName(osautil.GetTokenSecretNamePrefix(serviceAccount)), Namespace: serviceAccount.Namespace, Annotations: map[string]string{ api.ServiceAccountNameKey: serviceAccount.Name, api.ServiceAccountUIDKey: string(serviceAccount.UID), }, }, Type: api.SecretTypeServiceAccountToken, Data: map[string][]byte{}, } _, err := e.client.Secrets(tokenSecret.Namespace).Create(tokenSecret) if err != nil { return nil, err } // now we have to wait for the service account token controller to make this valid // TODO remove this once we have a create-token endpoint for i := 0; i <= tokenSecretWaitTimes; i++ { liveTokenSecret, err := e.client.Secrets(tokenSecret.Namespace).Get(tokenSecret.Name) if err != nil { return nil, err } if len(liveTokenSecret.Data[api.ServiceAccountTokenKey]) > 0 { return liveTokenSecret, nil } time.Sleep(wait.Jitter(tokenSecretWaitInterval, 0.0)) } // the token wasn't ever created, attempt deletion glog.Warningf("Deleting unfilled token secret %s/%s", tokenSecret.Namespace, tokenSecret.Name) if err := e.client.Secrets(tokenSecret.Namespace).Delete(tokenSecret.Name); (err != nil) && !kapierrors.IsNotFound(err) { util.HandleError(err) } return nil, fmt.Errorf("token never generated for %s", tokenSecret.Name) }
// Run creates a new token secret, waits for the service account token controller to fulfill it, then adds the token to the service account func (o *NewServiceAccountTokenOptions) Run() error { serviceAccount, err := o.SAClient.Get(o.SAName) if err != nil { return err } tokenSecret := &api.Secret{ ObjectMeta: api.ObjectMeta{ GenerateName: osautil.GetTokenSecretNamePrefix(serviceAccount), Namespace: serviceAccount.Namespace, Labels: o.Labels, Annotations: map[string]string{ api.ServiceAccountNameKey: serviceAccount.Name, }, }, Type: api.SecretTypeServiceAccountToken, Data: map[string][]byte{}, } persistedToken, err := o.SecretsClient.Create(tokenSecret) if err != nil { return err } // we need to wait for the service account token controller to make the new token valid tokenSecret, err = waitForToken(persistedToken, serviceAccount, o.Timeout, o.SecretsClient) if err != nil { return err } token, exists := tokenSecret.Data[api.ServiceAccountTokenKey] if !exists { return fmt.Errorf("service account token %q did not contain token data", tokenSecret.Name) } fmt.Fprintf(o.Out, string(token)) if util.IsTerminalWriter(o.Out) { // pretty-print for a TTY fmt.Fprintf(o.Out, "\n") } return nil }
func (e *DefaultExporter) Export(obj runtime.Object, exact bool) error { if meta, err := kapi.ObjectMetaFor(obj); err == nil { exportObjectMeta(meta, exact) } else { glog.V(4).Infof("Object of type %v does not have ObjectMeta: %v", reflect.TypeOf(obj), err) } ctx := kapi.NewContext() switch t := obj.(type) { case *kapi.Endpoints: endpoint.Strategy.PrepareForCreate(ctx, obj) case *kapi.ResourceQuota: resourcequota.Strategy.PrepareForCreate(ctx, obj) case *kapi.LimitRange: // TODO: this needs to be fixed // limitrange.Strategy.PrepareForCreate(obj) case *kapi.Node: node.Strategy.PrepareForCreate(ctx, obj) if exact { return nil } // Nodes are the only resources that allow direct status edits, therefore // we clear that without exact so that the node value can be reused. t.Status = kapi.NodeStatus{} case *kapi.Namespace: namespace.Strategy.PrepareForCreate(ctx, obj) case *kapi.PersistentVolumeClaim: persistentvolumeclaim.Strategy.PrepareForCreate(ctx, obj) case *kapi.PersistentVolume: persistentvolume.Strategy.PrepareForCreate(ctx, obj) case *kapi.ReplicationController: controller.Strategy.PrepareForCreate(ctx, obj) case *kapi.Pod: pod.Strategy.PrepareForCreate(ctx, obj) case *kapi.PodTemplate: case *kapi.Service: // TODO: service does not yet have a strategy t.Status = kapi.ServiceStatus{} if exact { return nil } if t.Spec.ClusterIP != kapi.ClusterIPNone { t.Spec.ClusterIP = "" } if t.Spec.Type == kapi.ServiceTypeNodePort { for i := range t.Spec.Ports { t.Spec.Ports[i].NodePort = 0 } } case *kapi.Secret: secret.Strategy.PrepareForCreate(ctx, obj) if exact { return nil } // secrets that are tied to the UID of a service account cannot be exported anyway if t.Type == kapi.SecretTypeServiceAccountToken || len(t.Annotations[kapi.ServiceAccountUIDKey]) > 0 { return ErrExportOmit } case *kapi.ServiceAccount: serviceaccount.Strategy.PrepareForCreate(ctx, obj) if exact { return nil } dockercfgSecretPrefix := osautil.GetDockercfgSecretNamePrefix(t) newImagePullSecrets := []kapi.LocalObjectReference{} for _, secretRef := range t.ImagePullSecrets { if strings.HasPrefix(secretRef.Name, dockercfgSecretPrefix) { continue } newImagePullSecrets = append(newImagePullSecrets, secretRef) } t.ImagePullSecrets = newImagePullSecrets tokenSecretPrefix := osautil.GetTokenSecretNamePrefix(t) newMountableSecrets := []kapi.ObjectReference{} for _, secretRef := range t.Secrets { if strings.HasPrefix(secretRef.Name, dockercfgSecretPrefix) || strings.HasPrefix(secretRef.Name, tokenSecretPrefix) { continue } newMountableSecrets = append(newMountableSecrets, secretRef) } t.Secrets = newMountableSecrets case *deployapi.DeploymentConfig: return deployrest.Strategy.Export(ctx, obj, exact) case *buildapi.BuildConfig: buildconfigrest.Strategy.PrepareForCreate(ctx, obj) // TODO: should be handled by prepare for create t.Status.LastVersion = 0 for i := range t.Spec.Triggers { if p := t.Spec.Triggers[i].ImageChange; p != nil { p.LastTriggeredImageID = "" } } case *buildapi.Build: buildrest.Strategy.PrepareForCreate(ctx, obj) // TODO: should be handled by prepare for create t.Status.Duration = 0 t.Status.Phase = buildapi.BuildPhaseNew t.Status.StartTimestamp = nil t.Status.CompletionTimestamp = nil if exact { return nil } if t.Status.Config != nil { t.Status.Config = &kapi.ObjectReference{Name: t.Status.Config.Name} } case *routeapi.Route: case *imageapi.Image: case *imageapi.ImageStream: if exact { return nil } // if we point to a docker image repository upstream, copy only the spec tags if len(t.Spec.DockerImageRepository) > 0 { t.Status = imageapi.ImageStreamStatus{} break } // create an image stream that mirrors (each spec tag points to the remote image stream) if len(t.Status.DockerImageRepository) > 0 { ref, err := imageapi.ParseDockerImageReference(t.Status.DockerImageRepository) if err != nil { return err } newSpec := imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{}, } for name, tag := range t.Status.Tags { if len(tag.Items) > 0 { // copy annotations existing := t.Spec.Tags[name] // point directly to that registry ref.Tag = name existing.From = &kapi.ObjectReference{ Kind: "DockerImage", Name: ref.String(), } newSpec.Tags[name] = existing } } for name, ref := range t.Spec.Tags { if _, ok := t.Status.Tags[name]; ok { continue } // TODO: potentially trim some of these newSpec.Tags[name] = ref } t.Spec = newSpec t.Status = imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{}, } break } // otherwise, try to snapshot the most recent image as spec items newSpec := imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{}, } for name, tag := range t.Status.Tags { if len(tag.Items) > 0 { // copy annotations existing := t.Spec.Tags[name] existing.From = &kapi.ObjectReference{ Kind: "DockerImage", Name: tag.Items[0].DockerImageReference, } newSpec.Tags[name] = existing } } t.Spec = newSpec t.Status = imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{}, } case *imageapi.ImageStreamTag: exportObjectMeta(&t.Image.ObjectMeta, exact) case *imageapi.ImageStreamImage: exportObjectMeta(&t.Image.ObjectMeta, exact) default: glog.V(4).Infof("No export strategy defined for objects of type %v", reflect.TypeOf(obj)) } return nil }
func TestExport(t *testing.T) { exporter := &defaultExporter{} baseSA := &kapi.ServiceAccount{} baseSA.Name = "my-sa" tests := []struct { name string object runtime.Object exact bool expectedObj runtime.Object expectedErr error }{ { name: "export deploymentConfig", object: deploytest.OkDeploymentConfig(1), expectedObj: &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{ Name: "config", }, LatestVersion: 0, Triggers: []deployapi.DeploymentTriggerPolicy{ deploytest.OkImageChangeTrigger(), }, Template: deploytest.OkDeploymentTemplate(), }, expectedErr: nil, }, { name: "export imageStream", object: &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: "test", Namespace: "other", }, Spec: imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "v1": { Annotations: map[string]string{"an": "annotation"}, }, }, }, Status: imageapi.ImageStreamStatus{ DockerImageRepository: "foo/bar", Tags: map[string]imageapi.TagEventList{ "v1": { Items: []imageapi.TagEvent{{Image: "the image"}}, }, }, }, }, expectedObj: &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: "test", Namespace: "", }, Spec: imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "v1": { From: &kapi.ObjectReference{ Kind: "DockerImage", Name: "foo/bar:v1", }, Annotations: map[string]string{"an": "annotation"}, }, }, }, Status: imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{}, }, }, expectedErr: nil, }, { name: "remove unexportable SA secrets", object: &kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{ Name: baseSA.Name, }, ImagePullSecrets: []kapi.LocalObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-pull-secret"}, }, Secrets: []kapi.ObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: osautil.GetTokenSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-mountable-secret"}, }, }, expectedObj: &kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{ Name: baseSA.Name, }, ImagePullSecrets: []kapi.LocalObjectReference{ {Name: "another-pull-secret"}, }, Secrets: []kapi.ObjectReference{ {Name: "another-mountable-secret"}, }, }, expectedErr: nil, }, { name: "do not remove unexportable SA secrets with exact", object: &kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{ Name: baseSA.Name, }, ImagePullSecrets: []kapi.LocalObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-pull-secret"}, }, Secrets: []kapi.ObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: osautil.GetTokenSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-mountable-secret"}, }, }, expectedObj: &kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{ Name: baseSA.Name, }, ImagePullSecrets: []kapi.LocalObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-pull-secret"}, }, Secrets: []kapi.ObjectReference{ {Name: osautil.GetDockercfgSecretNamePrefix(baseSA) + "-foo"}, {Name: osautil.GetTokenSecretNamePrefix(baseSA) + "-foo"}, {Name: "another-mountable-secret"}, }, }, exact: true, expectedErr: nil, }, } for _, test := range tests { if err := exporter.Export(test.object, test.exact); err != test.expectedErr { t.Errorf("error mismatch: expected %v, got %v", test.expectedErr, err) } if !reflect.DeepEqual(test.object, test.expectedObj) { t.Errorf("object mismatch: expected \n%v\ngot \n%v\n", test.expectedObj, test.object) } } }