From 6e1e7dbb245a8f1eaa466599ee8f1bba9dc8cfc7 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Mon, 27 Apr 2015 23:51:20 -0400 Subject: [PATCH] Add ServiceAccountToken SecretType --- contrib/completions/bash/kubectl | 1 + pkg/api/types.go | 18 ++++++++++++- pkg/api/v1/types.go | 18 ++++++++++++- pkg/api/v1beta1/types.go | 18 ++++++++++++- pkg/api/v1beta2/types.go | 18 ++++++++++++- pkg/api/v1beta3/types.go | 18 ++++++++++++- pkg/api/validation/validation.go | 6 +++++ pkg/api/validation/validation_test.go | 32 ++++++++++++++++++++++ pkg/kubectl/describe.go | 39 +++++++++++++++++++++++++++ 9 files changed, 163 insertions(+), 5 deletions(-) diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index f600d33f495..a87a1def4da 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -284,6 +284,7 @@ _kubectl_describe() must_have_one_noun+=("pod") must_have_one_noun+=("replicationcontroller") must_have_one_noun+=("resourcequota") + must_have_one_noun+=("secret") must_have_one_noun+=("service") } diff --git a/pkg/api/types.go b/pkg/api/types.go index 1173660ba55..d3525f6a8ee 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -1802,7 +1802,23 @@ const MaxSecretSize = 1 * 1024 * 1024 type SecretType string const ( - SecretTypeOpaque SecretType = "Opaque" // Default; arbitrary user-defined data + // SecretTypeOpaque is the default; arbitrary user-defined data + SecretTypeOpaque SecretType = "Opaque" + + // SecretTypeServiceAccountToken contains a token that identifies a service account to the API + // + // Required fields: + // - Secret.Annotations["kubernetes.io/service-account.name"] - the name of the ServiceAccount the token identifies + // - Secret.Annotations["kubernetes.io/service-account.uid"] - the UID of the ServiceAccount the token identifies + // - Secret.Data["token"] - a token that identifies the service account to the API + SecretTypeServiceAccountToken SecretType = "kubernetes.io/service-account-token" + + // ServiceAccountNameKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountNameKey = "kubernetes.io/service-account.name" + // ServiceAccountUIDKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountUIDKey = "kubernetes.io/service-account.uid" + // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets + ServiceAccountTokenKey = "token" ) type SecretList struct { diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index 50f2e4a4397..8c698af7560 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -1705,7 +1705,23 @@ const MaxSecretSize = 1 * 1024 * 1024 type SecretType string const ( - SecretTypeOpaque SecretType = "Opaque" // Default; arbitrary user-defined data + // SecretTypeOpaque is the default; arbitrary user-defined data + SecretTypeOpaque SecretType = "Opaque" + + // SecretTypeServiceAccountToken contains a token that identifies a service account to the API + // + // Required fields: + // - Secret.Annotations["kubernetes.io/service-account.name"] - the name of the ServiceAccount the token identifies + // - Secret.Annotations["kubernetes.io/service-account.uid"] - the UID of the ServiceAccount the token identifies + // - Secret.Data["token"] - a token that identifies the service account to the API + SecretTypeServiceAccountToken SecretType = "kubernetes.io/service-account-token" + + // ServiceAccountNameKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountNameKey = "kubernetes.io/service-account.name" + // ServiceAccountUIDKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountUIDKey = "kubernetes.io/service-account.uid" + // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets + ServiceAccountTokenKey = "token" ) type SecretList struct { diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 9dc4811ffd0..90558708656 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -1614,7 +1614,23 @@ const MaxSecretSize = 1 * 1024 * 1024 type SecretType string const ( - SecretTypeOpaque SecretType = "Opaque" // Default; arbitrary user-defined data + // SecretTypeOpaque is the default; arbitrary user-defined data + SecretTypeOpaque SecretType = "Opaque" + + // SecretTypeServiceAccountToken contains a token that identifies a service account to the API + // + // Required fields: + // - Secret.Annotations["kubernetes.io/service-account.name"] - the name of the ServiceAccount the token identifies + // - Secret.Annotations["kubernetes.io/service-account.uid"] - the UID of the ServiceAccount the token identifies + // - Secret.Data["token"] - a token that identifies the service account to the API + SecretTypeServiceAccountToken SecretType = "kubernetes.io/service-account-token" + + // ServiceAccountNameKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountNameKey = "kubernetes.io/service-account.name" + // ServiceAccountUIDKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountUIDKey = "kubernetes.io/service-account.uid" + // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets + ServiceAccountTokenKey = "token" ) type SecretList struct { diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index 574c3c30467..b2345994bc9 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -1689,7 +1689,23 @@ const MaxSecretSize = 1 * 1024 * 1024 type SecretType string const ( - SecretTypeOpaque SecretType = "Opaque" // Default; arbitrary user-defined data + // SecretTypeOpaque is the default; arbitrary user-defined data + SecretTypeOpaque SecretType = "Opaque" + + // SecretTypeServiceAccountToken contains a token that identifies a service account to the API + // + // Required fields: + // - Secret.Annotations["kubernetes.io/service-account.name"] - the name of the ServiceAccount the token identifies + // - Secret.Annotations["kubernetes.io/service-account.uid"] - the UID of the ServiceAccount the token identifies + // - Secret.Data["token"] - a token that identifies the service account to the API + SecretTypeServiceAccountToken SecretType = "kubernetes.io/service-account-token" + + // ServiceAccountNameKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountNameKey = "kubernetes.io/service-account.name" + // ServiceAccountUIDKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountUIDKey = "kubernetes.io/service-account.uid" + // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets + ServiceAccountTokenKey = "token" ) type SecretList struct { diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 40bcdabd63d..84d9d1d59ee 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -1705,7 +1705,23 @@ const MaxSecretSize = 1 * 1024 * 1024 type SecretType string const ( - SecretTypeOpaque SecretType = "Opaque" // Default; arbitrary user-defined data + // SecretTypeOpaque is the default; arbitrary user-defined data + SecretTypeOpaque SecretType = "Opaque" + + // SecretTypeServiceAccountToken contains a token that identifies a service account to the API + // + // Required fields: + // - Secret.Annotations["kubernetes.io/service-account.name"] - the name of the ServiceAccount the token identifies + // - Secret.Annotations["kubernetes.io/service-account.uid"] - the UID of the ServiceAccount the token identifies + // - Secret.Data["token"] - a token that identifies the service account to the API + SecretTypeServiceAccountToken SecretType = "kubernetes.io/service-account-token" + + // ServiceAccountNameKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountNameKey = "kubernetes.io/service-account.name" + // ServiceAccountUIDKey is the key of the required annotation for SecretTypeServiceAccountToken secrets + ServiceAccountUIDKey = "kubernetes.io/service-account.uid" + // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets + ServiceAccountTokenKey = "token" ) type SecretList struct { diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 56ac14ee6cb..fa012e6a83b 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -1244,6 +1244,12 @@ func ValidateSecret(secret *api.Secret) errs.ValidationErrorList { } switch secret.Type { + case api.SecretTypeServiceAccountToken: + // Only require Annotations[kubernetes.io/service-account.name] + // Additional fields (like Annotations[kubernetes.io/service-account.uid] and Data[token]) might be contributed later by a controller loop + if value := secret.Annotations[api.ServiceAccountNameKey]; len(value) == 0 { + allErrs = append(allErrs, errs.NewFieldRequired(fmt.Sprintf("metadata.annotations[%s]", api.ServiceAccountNameKey))) + } case api.SecretTypeOpaque, "": // no-op default: diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index e4b1b1f3da4..d20d02373d0 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -2961,6 +2961,7 @@ func TestValidateNamespaceUpdate(t *testing.T) { } func TestValidateSecret(t *testing.T) { + // Opaque secret validation validSecret := func() api.Secret { return api.Secret{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, @@ -2988,6 +2989,32 @@ func TestValidateSecret(t *testing.T) { } invalidKey.Data["a..b"] = []byte("whoops") + // kubernetes.io/service-account-token secret validation + validServiceAccountTokenSecret := func() api.Secret { + return api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + Annotations: map[string]string{ + api.ServiceAccountNameKey: "foo", + }, + }, + Type: api.SecretTypeServiceAccountToken, + Data: map[string][]byte{ + "data-1": []byte("bar"), + }, + } + } + + var ( + emptyTokenAnnotation = validServiceAccountTokenSecret() + missingTokenAnnotation = validServiceAccountTokenSecret() + missingTokenAnnotations = validServiceAccountTokenSecret() + ) + emptyTokenAnnotation.Annotations[api.ServiceAccountNameKey] = "" + delete(missingTokenAnnotation.Annotations, api.ServiceAccountNameKey) + missingTokenAnnotations.Annotations = nil + tests := map[string]struct { secret api.Secret valid bool @@ -2999,6 +3026,11 @@ func TestValidateSecret(t *testing.T) { "invalid namespace": {invalidNs, false}, "over max size": {overMaxSize, false}, "invalid key": {invalidKey, false}, + + "valid service-account-token secret": {validServiceAccountTokenSecret(), true}, + "empty service-account-token annotation": {emptyTokenAnnotation, false}, + "missing service-account-token annotation": {missingTokenAnnotation, false}, + "missing service-account-token annotations": {missingTokenAnnotations, false}, } for name, tc := range tests { diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 5b04dfca851..5c391cc6be9 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -64,6 +64,7 @@ func describerMap(c *client.Client) map[string]Describer { m := map[string]Describer{ "Pod": &PodDescriber{c}, "ReplicationController": &ReplicationControllerDescriber{c}, + "Secret": &SecretDescriber{c}, "Service": &ServiceDescriber{c}, "Minion": &NodeDescriber{c}, "Node": &NodeDescriber{c}, @@ -421,6 +422,44 @@ func describeReplicationController(controller *api.ReplicationController, events }) } +// SecretDescriber generates information about a secret +type SecretDescriber struct { + client.Interface +} + +func (d *SecretDescriber) Describe(namespace, name string) (string, error) { + c := d.Secrets(namespace) + + secret, err := c.Get(name) + if err != nil { + return "", err + } + + return describeSecret(secret) +} + +func describeSecret(secret *api.Secret) (string, error) { + return tabbedString(func(out io.Writer) error { + fmt.Fprintf(out, "Name:\t%s\n", secret.Name) + fmt.Fprintf(out, "Labels:\t%s\n", formatLabels(secret.Labels)) + fmt.Fprintf(out, "Annotations:\t%s\n", formatLabels(secret.Annotations)) + + fmt.Fprintf(out, "\nType:\t%s\n", secret.Type) + + fmt.Fprintf(out, "\nData\n====\n") + for k, v := range secret.Data { + switch { + case k == api.ServiceAccountTokenKey && secret.Type == api.SecretTypeServiceAccountToken: + fmt.Fprintf(out, "%s:\t%s\n", k, string(v)) + default: + fmt.Fprintf(out, "%s:\t%d bytes\n", k, len(v)) + } + } + + return nil + }) +} + // ServiceDescriber generates information about a service. type ServiceDescriber struct { client.Interface