From be0f2d29305a20d12d83e3892ad7aab967064a57 Mon Sep 17 00:00:00 2001 From: deads2k Date: Wed, 6 May 2015 10:09:18 -0400 Subject: [PATCH] add dockercfg secret types --- pkg/api/types.go | 9 ++++++ pkg/api/v1/types.go | 11 +++++++ pkg/api/v1beta1/types.go | 11 +++++++ pkg/api/v1beta2/types.go | 11 +++++++ pkg/api/v1beta3/types.go | 11 +++++++ pkg/api/validation/validation.go | 13 +++++++++ pkg/api/validation/validation_test.go | 42 +++++++++++++++++++++++++++ pkg/credentialprovider/config.go | 27 ++++++++++++----- pkg/credentialprovider/config_test.go | 30 +++++++++++++++++++ 9 files changed, 158 insertions(+), 7 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index 53c3c55826c..dd366b36ee2 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -1859,6 +1859,15 @@ const ( ServiceAccountTokenKey = "token" // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" + + // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg + // + // Required fields: + // - Secret.Data[".dockercfg"] - a serialized ~/.dockercfg file + SecretTypeDockercfg SecretType = "kubernetes.io/dockercfg" + + // DockerConfigKey is the key of the required data for SecretTypeDockercfg secrets + DockerConfigKey = ".dockercfg" ) type SecretList struct { diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index ed6a6fc3732..464459c2bcc 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -1754,6 +1754,17 @@ const ( ServiceAccountUIDKey = "kubernetes.io/service-account.uid" // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets ServiceAccountTokenKey = "token" + // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets + ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" + + // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg + // + // Required fields: + // - Secret.Data[".dockercfg"] - a serialized ~/.dockercfg file + SecretTypeDockercfg SecretType = "kubernetes.io/dockercfg" + + // DockerConfigKey is the key of the required data for SecretTypeDockercfg secrets + DockerConfigKey = ".dockercfg" ) type SecretList struct { diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 07dfd30a8de..b7807e84d1a 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -1657,6 +1657,17 @@ const ( ServiceAccountUIDKey = "kubernetes.io/service-account.uid" // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets ServiceAccountTokenKey = "token" + // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets + ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" + + // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg + // + // Required fields: + // - Secret.Data[".dockercfg"] - a serialized ~/.dockercfg file + SecretTypeDockercfg SecretType = "kubernetes.io/dockercfg" + + // DockerConfigKey is the key of the required data for SecretTypeDockercfg secrets + DockerConfigKey = ".dockercfg" ) type SecretList struct { diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index 39374245112..1e6caa11338 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -1734,6 +1734,17 @@ const ( ServiceAccountUIDKey = "kubernetes.io/service-account.uid" // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets ServiceAccountTokenKey = "token" + // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets + ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" + + // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg + // + // Required fields: + // - Secret.Data[".dockercfg"] - a serialized ~/.dockercfg file + SecretTypeDockercfg SecretType = "kubernetes.io/dockercfg" + + // DockerConfigKey is the key of the required data for SecretTypeDockercfg secrets + DockerConfigKey = ".dockercfg" ) type SecretList struct { diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index e2efa44b082..5077445a3fe 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -1754,6 +1754,17 @@ const ( ServiceAccountUIDKey = "kubernetes.io/service-account.uid" // ServiceAccountTokenKey is the key of the required data for SecretTypeServiceAccountToken secrets ServiceAccountTokenKey = "token" + // ServiceAccountKubeconfigKey is the key of the optional kubeconfig data for SecretTypeServiceAccountToken secrets + ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" + + // SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg + // + // Required fields: + // - Secret.Data[".dockercfg"] - a serialized ~/.dockercfg file + SecretTypeDockercfg SecretType = "kubernetes.io/dockercfg" + + // DockerConfigKey is the key of the required data for SecretTypeDockercfg secrets + DockerConfigKey = ".dockercfg" ) type SecretList struct { diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 5afc37d3dc5..2b519b309ea 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -17,6 +17,7 @@ limitations under the License. package validation import ( + "encoding/json" "fmt" "net" "path" @@ -1295,6 +1296,18 @@ func ValidateSecret(secret *api.Secret) errs.ValidationErrorList { } case api.SecretTypeOpaque, "": // no-op + case api.SecretTypeDockercfg: + dockercfgBytes, exists := secret.Data[api.DockerConfigKey] + if !exists { + allErrs = append(allErrs, errs.NewFieldRequired(fmt.Sprintf("data[%s]", api.DockerConfigKey))) + break + } + + // make sure that the content is well-formed json. + if err := json.Unmarshal(dockercfgBytes, &map[string]interface{}{}); err != nil { + allErrs = append(allErrs, errs.NewFieldInvalid(fmt.Sprintf("data[%s]", api.DockerConfigKey), "", err.Error())) + } + default: // no-op } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index b233b08f97b..bb780eee1a2 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -2949,6 +2949,48 @@ func TestValidateSecret(t *testing.T) { } } +func TestValidateDockerConfigSecret(t *testing.T) { + validDockerSecret := func() api.Secret { + return api.Secret{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Type: api.SecretTypeDockercfg, + Data: map[string][]byte{ + api.DockerConfigKey: []byte(`{"https://index.docker.io/v1/": {"auth": "Y2x1ZWRyb29sZXIwMDAxOnBhc3N3b3Jk","email": "fake@example.com"}}`), + }, + } + } + + var ( + missingDockerConfigKey = validDockerSecret() + emptyDockerConfigKey = validDockerSecret() + invalidDockerConfigKey = validDockerSecret() + ) + + delete(missingDockerConfigKey.Data, api.DockerConfigKey) + emptyDockerConfigKey.Data[api.DockerConfigKey] = []byte("") + invalidDockerConfigKey.Data[api.DockerConfigKey] = []byte("bad") + + tests := map[string]struct { + secret api.Secret + valid bool + }{ + "valid": {validDockerSecret(), true}, + "missing dockercfg": {missingDockerConfigKey, false}, + "empty dockercfg": {emptyDockerConfigKey, false}, + "invalid dockercfg": {invalidDockerConfigKey, false}, + } + + for name, tc := range tests { + errs := ValidateSecret(&tc.secret) + if tc.valid && len(errs) > 0 { + t.Errorf("%v: Unexpected error: %v", name, errs) + } + if !tc.valid && len(errs) == 0 { + t.Errorf("%v: Unexpected non-error", name) + } + } +} + func TestValidateEndpoints(t *testing.T) { successCases := map[string]api.Endpoints{ "simple endpoint": { diff --git a/pkg/credentialprovider/config.go b/pkg/credentialprovider/config.go index aa74e28fc94..d9ffe4803f3 100644 --- a/pkg/credentialprovider/config.go +++ b/pkg/credentialprovider/config.go @@ -147,17 +147,17 @@ func readDockerConfigFileFromBytes(contents []byte) (cfg DockerConfig, err error return } -// dockerConfigEntryWithAuth is used solely for deserializing the Auth field +// DockerConfigEntryWithAuth is used solely for deserializing the Auth field // into a dockerConfigEntry during JSON deserialization. -type dockerConfigEntryWithAuth struct { - Username string - Password string - Email string - Auth string +type DockerConfigEntryWithAuth struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Email string `json:"email,omitempty"` + Auth string `json:"auth,omitempty"` } func (ident *DockerConfigEntry) UnmarshalJSON(data []byte) error { - var tmp dockerConfigEntryWithAuth + var tmp DockerConfigEntryWithAuth err := json.Unmarshal(data, &tmp) if err != nil { return err @@ -194,3 +194,16 @@ func decodeDockerConfigFieldAuth(field string) (username, password string, err e return } + +func (ident DockerConfigEntry) ConvertToDockerConfigCompatible() DockerConfigEntryWithAuth { + ret := DockerConfigEntryWithAuth{ident.Username, ident.Password, ident.Email, ""} + ret.Auth = encodeDockerConfigFieldAuth(ident.Username, ident.Password) + + return ret +} + +func encodeDockerConfigFieldAuth(username, password string) string { + fieldValue := username + ":" + password + + return base64.StdEncoding.EncodeToString([]byte(fieldValue)) +} diff --git a/pkg/credentialprovider/config_test.go b/pkg/credentialprovider/config_test.go index 86532d271c8..4f52c7e4454 100644 --- a/pkg/credentialprovider/config_test.go +++ b/pkg/credentialprovider/config_test.go @@ -166,3 +166,33 @@ func TestDecodeDockerConfigFieldAuth(t *testing.T) { } } } + +func TestDockerConfigEntryJSONCompatibleEncode(t *testing.T) { + tests := []struct { + input DockerConfigEntry + expect []byte + }{ + // simple case, just decode the fields + { + expect: []byte(`{"username":"foo","password":"bar","email":"foo@example.com","auth":"Zm9vOmJhcg=="}`), + input: DockerConfigEntry{ + Username: "foo", + Password: "bar", + Email: "foo@example.com", + }, + }, + } + + for i, tt := range tests { + toEncode := tt.input.ConvertToDockerConfigCompatible() + + actual, err := json.Marshal(toEncode) + if err != nil { + t.Errorf("case %d: unexpected error: %v", i, err) + } + + if string(tt.expect) != string(actual) { + t.Errorf("case %d: expected %v, got %v", i, string(tt.expect), string(actual)) + } + } +}