diff --git a/cmd/kubelet/app/BUILD b/cmd/kubelet/app/BUILD index c68f6115a5..610c64bf36 100644 --- a/cmd/kubelet/app/BUILD +++ b/cmd/kubelet/app/BUILD @@ -48,6 +48,7 @@ go_library( "//pkg/credentialprovider/aws:go_default_library", "//pkg/credentialprovider/azure:go_default_library", "//pkg/credentialprovider/gcp:go_default_library", + "//pkg/credentialprovider/rancher:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet:go_default_library", "//pkg/kubelet/cadvisor:go_default_library", diff --git a/cmd/kubelet/app/plugins.go b/cmd/kubelet/app/plugins.go index a9aea2c365..e500bb786c 100644 --- a/cmd/kubelet/app/plugins.go +++ b/cmd/kubelet/app/plugins.go @@ -22,6 +22,7 @@ import ( _ "k8s.io/kubernetes/pkg/credentialprovider/aws" _ "k8s.io/kubernetes/pkg/credentialprovider/azure" _ "k8s.io/kubernetes/pkg/credentialprovider/gcp" + _ "k8s.io/kubernetes/pkg/credentialprovider/rancher" // Network plugins "k8s.io/kubernetes/pkg/kubelet/network" "k8s.io/kubernetes/pkg/kubelet/network/cni" diff --git a/pkg/credentialprovider/BUILD b/pkg/credentialprovider/BUILD index e8e59ab2bf..a5d2210ecb 100644 --- a/pkg/credentialprovider/BUILD +++ b/pkg/credentialprovider/BUILD @@ -51,6 +51,7 @@ filegroup( "//pkg/credentialprovider/aws:all-srcs", "//pkg/credentialprovider/azure:all-srcs", "//pkg/credentialprovider/gcp:all-srcs", + "//pkg/credentialprovider/rancher:all-srcs", ], tags = ["automanaged"], ) diff --git a/pkg/credentialprovider/rancher/BUILD b/pkg/credentialprovider/rancher/BUILD new file mode 100644 index 0000000000..6a072132b0 --- /dev/null +++ b/pkg/credentialprovider/rancher/BUILD @@ -0,0 +1,47 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = ["rancher_registry_credentials_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//pkg/credentialprovider:go_default_library", + "//vendor:github.com/rancher/go-rancher/client", + ], +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "rancher_registry_credentials.go", + ], + tags = ["automanaged"], + deps = [ + "//pkg/credentialprovider:go_default_library", + "//vendor:github.com/golang/glog", + "//vendor:github.com/rancher/go-rancher/client", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/credentialprovider/rancher/doc.go b/pkg/credentialprovider/rancher/doc.go new file mode 100644 index 0000000000..ec9f045928 --- /dev/null +++ b/pkg/credentialprovider/rancher/doc.go @@ -0,0 +1 @@ +package rancher_credentials diff --git a/pkg/credentialprovider/rancher/rancher_registry_credentials.go b/pkg/credentialprovider/rancher/rancher_registry_credentials.go new file mode 100644 index 0000000000..4222e2986f --- /dev/null +++ b/pkg/credentialprovider/rancher/rancher_registry_credentials.go @@ -0,0 +1,129 @@ +package rancher_credentials + +import ( + "os" + "time" + + "github.com/golang/glog" + "github.com/rancher/go-rancher/client" + "k8s.io/kubernetes/pkg/credentialprovider" +) + +// rancher provider +type rancherProvider struct { + credGetter credentialsGetter +} + +// credentials getter from Rancher private registry +type rancherCredentialsGetter struct { + client *client.RancherClient +} + +type rConfig struct { + Global configGlobal +} + +// An interface for testing purposes. +type credentialsGetter interface { + getCredentials() []registryCredential +} + +type configGlobal struct { + CattleURL string `gcfg:"cattle-url"` + CattleAccessKey string `gcfg:"cattle-access-key"` + CattleSecretKey string `gcfg:"cattle-secret-key"` +} + +type registryCredential struct { + credential *client.RegistryCredential + serverIP string +} + +var rancherGetter = &rancherCredentialsGetter{} + +func init() { + credentialprovider.RegisterCredentialProvider("rancher-registry-creds", + &credentialprovider.CachingDockerConfigProvider{ + Provider: &rancherProvider{rancherGetter}, + Lifetime: 30 * time.Second, + }) +} + +func (p *rancherProvider) Enabled() bool { + client, err := getRancherClient() + if err != nil { + return false + } + if client == nil { + return false + } + + rancherGetter.client = client + return true +} + +// LazyProvide implements DockerConfigProvider. Should never be called. +func (p *rancherProvider) LazyProvide() *credentialprovider.DockerConfigEntry { + return nil +} + +// Provide implements DockerConfigProvider.Provide, refreshing Rancher tokens on demand +func (p *rancherProvider) Provide() credentialprovider.DockerConfig { + cfg := credentialprovider.DockerConfig{} + for _, cred := range p.credGetter.getCredentials() { + entry := credentialprovider.DockerConfigEntry{ + Username: cred.credential.PublicValue, + Password: cred.credential.SecretValue, + Email: cred.credential.Email, + } + cfg[cred.serverIP] = entry + } + + return cfg +} + +func (g *rancherCredentialsGetter) getCredentials() []registryCredential { + var registryCreds []registryCredential + credColl, err := g.client.RegistryCredential.List(client.NewListOpts()) + if err != nil { + glog.Errorf("Failed to pull registry credentials from rancher %v", err) + return registryCreds + } + for _, cred := range credColl.Data { + registry := &client.Registry{} + if err = g.client.GetLink(cred.Resource, "registry", registry); err != nil { + glog.Errorf("Failed to pull registry from rancher %v", err) + return registryCreds + } + registryCred := registryCredential{ + credential: &cred, + serverIP: registry.ServerAddress, + } + registryCreds = append(registryCreds, registryCred) + } + return registryCreds +} + +func getRancherClient() (*client.RancherClient, error) { + url := os.Getenv("CATTLE_URL") + accessKey := os.Getenv("CATTLE_ACCESS_KEY") + secretKey := os.Getenv("CATTLE_SECRET_KEY") + + if url == "" || accessKey == "" || secretKey == "" { + return nil, nil + } + + conf := rConfig{ + Global: configGlobal{ + CattleURL: url, + CattleAccessKey: accessKey, + CattleSecretKey: secretKey, + }, + } + + return client.NewRancherClient(&client.ClientOpts{ + Url: conf.Global.CattleURL, + AccessKey: conf.Global.CattleAccessKey, + SecretKey: conf.Global.CattleSecretKey, + }) +} diff --git a/pkg/credentialprovider/rancher/rancher_registry_credentials_test.go b/pkg/credentialprovider/rancher/rancher_registry_credentials_test.go new file mode 100644 index 0000000000..271ad947a6 --- /dev/null +++ b/pkg/credentialprovider/rancher/rancher_registry_credentials_test.go @@ -0,0 +1,104 @@ +package rancher_credentials + +import ( + "path" + "testing" + + "github.com/rancher/go-rancher/client" + "k8s.io/kubernetes/pkg/credentialprovider" +) + +const username = "foo" +const password = "qwerty" +const email = "foo@bar.baz" + +var serverAddresses = []string{"quay.io", "192.168.5.0"} + +type testCredentialsGetter struct { + client *client.RancherClient +} + +func (p *testCredentialsGetter) getCredentials() []registryCredential { + var registryCreds []registryCredential + + for _, serverAddress := range serverAddresses { + cred := &client.RegistryCredential{ + PublicValue: username, + SecretValue: password, + Email: email, + } + registryCred := registryCredential{ + credential: cred, + serverIP: serverAddress, + } + registryCreds = append(registryCreds, registryCred) + } + + return registryCreds +} + +func TestRancherCredentialsProvide(t *testing.T) { + image := "foo/bar" + + url := "http://localhost:8080" + accessKey := "B481F55E0C48C546E094" + secretKey := "dND2fBcytWWvCRJ8LvqnYcjyNfEkaikvfVxk2C5r" + conf := rConfig{ + Global: configGlobal{ + CattleURL: url, + CattleAccessKey: accessKey, + CattleSecretKey: secretKey, + }, + } + + rancherClient, _ := client.NewRancherClient(&client.ClientOpts{ + Url: conf.Global.CattleURL, + AccessKey: conf.Global.CattleAccessKey, + SecretKey: conf.Global.CattleSecretKey, + }) + + testGetter := &testCredentialsGetter{ + client: rancherClient, + } + + provider := &rancherProvider{ + credGetter: testGetter, + } + + keyring := &credentialprovider.BasicDockerKeyring{} + keyring.Add(provider.Provide()) + + for _, registry := range serverAddresses { + fullImagePath := path.Join(registry, image) + creds, ok := keyring.Lookup(fullImagePath) + if !ok { + t.Errorf("Didn't find expected image: %s", fullImagePath) + return + } + + if len(creds) > 1 { + t.Errorf("Expected 1 result, received %v", len(creds)) + } + + val := creds[0] + + if username != val.Username { + t.Errorf("Unexpected username value, want: %s, got: %s", username, val.Username) + } + if password != val.Password { + t.Errorf("Unexpected password value, want: %s, got: %s", password, val.Password) + } + if email != val.Email { + t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email) + } + } + + // try to fetch non-existing registry + fullImagePath := path.Join("1.1.1.1", image) + _, ok := keyring.Lookup(fullImagePath) + if ok { + t.Errorf("Found non-existing image: %s", fullImagePath) + } + + return +}