Commit 91f19e3d authored by Brendan Burns's avatar Brendan Burns

Add an azure container registry credential provider.

parent a8c5c812
...@@ -45,6 +45,7 @@ go_library( ...@@ -45,6 +45,7 @@ go_library(
"//pkg/cloudprovider/providers:go_default_library", "//pkg/cloudprovider/providers:go_default_library",
"//pkg/credentialprovider:go_default_library", "//pkg/credentialprovider:go_default_library",
"//pkg/credentialprovider/aws:go_default_library", "//pkg/credentialprovider/aws:go_default_library",
"//pkg/credentialprovider/azure:go_default_library",
"//pkg/credentialprovider/gcp:go_default_library", "//pkg/credentialprovider/gcp:go_default_library",
"//pkg/genericapiserver/authorizer:go_default_library", "//pkg/genericapiserver/authorizer:go_default_library",
"//pkg/healthz:go_default_library", "//pkg/healthz:go_default_library",
......
...@@ -20,6 +20,7 @@ package app ...@@ -20,6 +20,7 @@ package app
import ( import (
// Credential providers // Credential providers
_ "k8s.io/kubernetes/pkg/credentialprovider/aws" _ "k8s.io/kubernetes/pkg/credentialprovider/aws"
_ "k8s.io/kubernetes/pkg/credentialprovider/azure"
_ "k8s.io/kubernetes/pkg/credentialprovider/gcp" _ "k8s.io/kubernetes/pkg/credentialprovider/gcp"
// Network plugins // Network plugins
"k8s.io/kubernetes/pkg/kubelet/network" "k8s.io/kubernetes/pkg/kubelet/network"
......
...@@ -43,6 +43,7 @@ authorization-rbac-super-user ...@@ -43,6 +43,7 @@ authorization-rbac-super-user
authorization-webhook-cache-authorized-ttl authorization-webhook-cache-authorized-ttl
authorization-webhook-cache-unauthorized-ttl authorization-webhook-cache-unauthorized-ttl
authorization-webhook-config-file authorization-webhook-config-file
azure-container-registry-config
babysit-daemons babysit-daemons
basic-auth-file basic-auth-file
bench-pods bench-pods
......
...@@ -50,7 +50,7 @@ func TestReconcileLoadBalancerAddPort(t *testing.T) { ...@@ -50,7 +50,7 @@ func TestReconcileLoadBalancerAddPort(t *testing.T) {
} }
// ensure we got a frontend ip configuration // ensure we got a frontend ip configuration
if len(*lb.Properties.FrontendIPConfigurations) != 1 { if len(*lb.FrontendIPConfigurations) != 1 {
t.Error("Expected the loadbalancer to have a frontend ip configuration") t.Error("Expected the loadbalancer to have a frontend ip configuration")
} }
...@@ -79,7 +79,7 @@ func TestReconcileLoadBalancerNodeHealth(t *testing.T) { ...@@ -79,7 +79,7 @@ func TestReconcileLoadBalancerNodeHealth(t *testing.T) {
} }
// ensure we got a frontend ip configuration // ensure we got a frontend ip configuration
if len(*lb.Properties.FrontendIPConfigurations) != 1 { if len(*lb.FrontendIPConfigurations) != 1 {
t.Error("Expected the loadbalancer to have a frontend ip configuration") t.Error("Expected the loadbalancer to have a frontend ip configuration")
} }
...@@ -110,7 +110,7 @@ func TestReconcileLoadBalancerRemoveAllPortsRemovesFrontendConfig(t *testing.T) ...@@ -110,7 +110,7 @@ func TestReconcileLoadBalancerRemoveAllPortsRemovesFrontendConfig(t *testing.T)
} }
// ensure we abandoned the frontend ip configuration // ensure we abandoned the frontend ip configuration
if len(*lb.Properties.FrontendIPConfigurations) != 0 { if len(*lb.FrontendIPConfigurations) != 0 {
t.Error("Expected the loadbalancer to have no frontend ip configuration") t.Error("Expected the loadbalancer to have no frontend ip configuration")
} }
...@@ -262,14 +262,14 @@ func getTestLoadBalancer(services ...v1.Service) network.LoadBalancer { ...@@ -262,14 +262,14 @@ func getTestLoadBalancer(services ...v1.Service) network.LoadBalancer {
ruleName := getRuleName(&service, port) ruleName := getRuleName(&service, port)
rules = append(rules, network.LoadBalancingRule{ rules = append(rules, network.LoadBalancingRule{
Name: to.StringPtr(ruleName), Name: to.StringPtr(ruleName),
Properties: &network.LoadBalancingRulePropertiesFormat{ LoadBalancingRulePropertiesFormat: &network.LoadBalancingRulePropertiesFormat{
FrontendPort: to.Int32Ptr(port.Port), FrontendPort: to.Int32Ptr(port.Port),
BackendPort: to.Int32Ptr(port.Port), BackendPort: to.Int32Ptr(port.Port),
}, },
}) })
probes = append(probes, network.Probe{ probes = append(probes, network.Probe{
Name: to.StringPtr(ruleName), Name: to.StringPtr(ruleName),
Properties: &network.ProbePropertiesFormat{ ProbePropertiesFormat: &network.ProbePropertiesFormat{
Port: to.Int32Ptr(port.NodePort), Port: to.Int32Ptr(port.NodePort),
}, },
}) })
...@@ -277,7 +277,7 @@ func getTestLoadBalancer(services ...v1.Service) network.LoadBalancer { ...@@ -277,7 +277,7 @@ func getTestLoadBalancer(services ...v1.Service) network.LoadBalancer {
} }
lb := network.LoadBalancer{ lb := network.LoadBalancer{
Properties: &network.LoadBalancerPropertiesFormat{ LoadBalancerPropertiesFormat: &network.LoadBalancerPropertiesFormat{
LoadBalancingRules: &rules, LoadBalancingRules: &rules,
Probes: &probes, Probes: &probes,
}, },
...@@ -304,7 +304,7 @@ func getTestSecurityGroup(services ...v1.Service) network.SecurityGroup { ...@@ -304,7 +304,7 @@ func getTestSecurityGroup(services ...v1.Service) network.SecurityGroup {
for _, src := range sources { for _, src := range sources {
rules = append(rules, network.SecurityRule{ rules = append(rules, network.SecurityRule{
Name: to.StringPtr(ruleName), Name: to.StringPtr(ruleName),
Properties: &network.SecurityRulePropertiesFormat{ SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{
SourceAddressPrefix: to.StringPtr(src), SourceAddressPrefix: to.StringPtr(src),
DestinationPortRange: to.StringPtr(fmt.Sprintf("%d", port.Port)), DestinationPortRange: to.StringPtr(fmt.Sprintf("%d", port.Port)),
}, },
...@@ -314,7 +314,7 @@ func getTestSecurityGroup(services ...v1.Service) network.SecurityGroup { ...@@ -314,7 +314,7 @@ func getTestSecurityGroup(services ...v1.Service) network.SecurityGroup {
} }
sg := network.SecurityGroup{ sg := network.SecurityGroup{
Properties: &network.SecurityGroupPropertiesFormat{ SecurityGroupPropertiesFormat: &network.SecurityGroupPropertiesFormat{
SecurityRules: &rules, SecurityRules: &rules,
}, },
} }
...@@ -329,10 +329,10 @@ func validateLoadBalancer(t *testing.T, loadBalancer network.LoadBalancer, servi ...@@ -329,10 +329,10 @@ func validateLoadBalancer(t *testing.T, loadBalancer network.LoadBalancer, servi
expectedRuleCount++ expectedRuleCount++
wantedRuleName := getRuleName(&svc, wantedRule) wantedRuleName := getRuleName(&svc, wantedRule)
foundRule := false foundRule := false
for _, actualRule := range *loadBalancer.Properties.LoadBalancingRules { for _, actualRule := range *loadBalancer.LoadBalancingRules {
if strings.EqualFold(*actualRule.Name, wantedRuleName) && if strings.EqualFold(*actualRule.Name, wantedRuleName) &&
*actualRule.Properties.FrontendPort == wantedRule.Port && *actualRule.FrontendPort == wantedRule.Port &&
*actualRule.Properties.BackendPort == wantedRule.Port { *actualRule.BackendPort == wantedRule.Port {
foundRule = true foundRule = true
break break
} }
...@@ -344,38 +344,38 @@ func validateLoadBalancer(t *testing.T, loadBalancer network.LoadBalancer, servi ...@@ -344,38 +344,38 @@ func validateLoadBalancer(t *testing.T, loadBalancer network.LoadBalancer, servi
foundProbe := false foundProbe := false
if serviceapi.NeedsHealthCheck(&svc) { if serviceapi.NeedsHealthCheck(&svc) {
path, port := serviceapi.GetServiceHealthCheckPathPort(&svc) path, port := serviceapi.GetServiceHealthCheckPathPort(&svc)
for _, actualProbe := range *loadBalancer.Properties.Probes { for _, actualProbe := range *loadBalancer.Probes {
if strings.EqualFold(*actualProbe.Name, wantedRuleName) && if strings.EqualFold(*actualProbe.Name, wantedRuleName) &&
*actualProbe.Properties.Port == port && *actualProbe.Port == port &&
*actualProbe.Properties.RequestPath == path && *actualProbe.RequestPath == path &&
actualProbe.Properties.Protocol == network.ProbeProtocolHTTP { actualProbe.Protocol == network.ProbeProtocolHTTP {
foundProbe = true foundProbe = true
break break
} }
} }
} else { } else {
for _, actualProbe := range *loadBalancer.Properties.Probes { for _, actualProbe := range *loadBalancer.Probes {
if strings.EqualFold(*actualProbe.Name, wantedRuleName) && if strings.EqualFold(*actualProbe.Name, wantedRuleName) &&
*actualProbe.Properties.Port == wantedRule.NodePort { *actualProbe.Port == wantedRule.NodePort {
foundProbe = true foundProbe = true
break break
} }
} }
} }
if !foundProbe { if !foundProbe {
for _, actualProbe := range *loadBalancer.Properties.Probes { for _, actualProbe := range *loadBalancer.Probes {
t.Logf("Probe: %s %d", *actualProbe.Name, *actualProbe.Properties.Port) t.Logf("Probe: %s %d", *actualProbe.Name, *actualProbe.Port)
} }
t.Errorf("Expected loadbalancer probe but didn't find it: %q", wantedRuleName) t.Errorf("Expected loadbalancer probe but didn't find it: %q", wantedRuleName)
} }
} }
} }
lenRules := len(*loadBalancer.Properties.LoadBalancingRules) lenRules := len(*loadBalancer.LoadBalancingRules)
if lenRules != expectedRuleCount { if lenRules != expectedRuleCount {
t.Errorf("Expected the loadbalancer to have %d rules. Found %d.\n%v", expectedRuleCount, lenRules, loadBalancer.Properties.LoadBalancingRules) t.Errorf("Expected the loadbalancer to have %d rules. Found %d.\n%v", expectedRuleCount, lenRules, loadBalancer.LoadBalancingRules)
} }
lenProbes := len(*loadBalancer.Properties.Probes) lenProbes := len(*loadBalancer.Probes)
if lenProbes != expectedRuleCount { if lenProbes != expectedRuleCount {
t.Errorf("Expected the loadbalancer to have %d probes. Found %d.", expectedRuleCount, lenProbes) t.Errorf("Expected the loadbalancer to have %d probes. Found %d.", expectedRuleCount, lenProbes)
} }
...@@ -391,10 +391,10 @@ func validateSecurityGroup(t *testing.T, securityGroup network.SecurityGroup, se ...@@ -391,10 +391,10 @@ func validateSecurityGroup(t *testing.T, securityGroup network.SecurityGroup, se
expectedRuleCount++ expectedRuleCount++
wantedRuleName := getRuleName(&svc, wantedRule) wantedRuleName := getRuleName(&svc, wantedRule)
foundRule := false foundRule := false
for _, actualRule := range *securityGroup.Properties.SecurityRules { for _, actualRule := range *securityGroup.SecurityRules {
if strings.EqualFold(*actualRule.Name, wantedRuleName) && if strings.EqualFold(*actualRule.Name, wantedRuleName) &&
*actualRule.Properties.SourceAddressPrefix == source && *actualRule.SourceAddressPrefix == source &&
*actualRule.Properties.DestinationPortRange == fmt.Sprintf("%d", wantedRule.Port) { *actualRule.DestinationPortRange == fmt.Sprintf("%d", wantedRule.Port) {
foundRule = true foundRule = true
break break
} }
...@@ -406,7 +406,7 @@ func validateSecurityGroup(t *testing.T, securityGroup network.SecurityGroup, se ...@@ -406,7 +406,7 @@ func validateSecurityGroup(t *testing.T, securityGroup network.SecurityGroup, se
} }
} }
lenRules := len(*securityGroup.Properties.SecurityRules) lenRules := len(*securityGroup.SecurityRules)
if lenRules != expectedRuleCount { if lenRules != expectedRuleCount {
t.Errorf("Expected the loadbalancer to have %d rules. Found %d.\n", expectedRuleCount, lenRules) t.Errorf("Expected the loadbalancer to have %d rules. Found %d.\n", expectedRuleCount, lenRules)
} }
...@@ -420,7 +420,7 @@ func TestSecurityRulePriorityPicksNextAvailablePriority(t *testing.T) { ...@@ -420,7 +420,7 @@ func TestSecurityRulePriorityPicksNextAvailablePriority(t *testing.T) {
var i int32 var i int32
for i = loadBalancerMinimumPriority; i < expectedPriority; i++ { for i = loadBalancerMinimumPriority; i < expectedPriority; i++ {
rules = append(rules, network.SecurityRule{ rules = append(rules, network.SecurityRule{
Properties: &network.SecurityRulePropertiesFormat{ SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{
Priority: to.Int32Ptr(i), Priority: to.Int32Ptr(i),
}, },
}) })
...@@ -442,7 +442,7 @@ func TestSecurityRulePriorityFailsIfExhausted(t *testing.T) { ...@@ -442,7 +442,7 @@ func TestSecurityRulePriorityFailsIfExhausted(t *testing.T) {
var i int32 var i int32
for i = loadBalancerMinimumPriority; i < loadBalancerMaximumPriority; i++ { for i = loadBalancerMinimumPriority; i < loadBalancerMaximumPriority; i++ {
rules = append(rules, network.SecurityRule{ rules = append(rules, network.SecurityRule{
Properties: &network.SecurityRulePropertiesFormat{ SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{
Priority: to.Int32Ptr(i), Priority: to.Int32Ptr(i),
}, },
}) })
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["azure_credentials.go"],
tags = ["automanaged"],
deps = [
"//pkg/cloudprovider/providers/azure:go_default_library",
"//pkg/credentialprovider:go_default_library",
"//vendor:github.com/Azure/azure-sdk-for-go/arm/containerregistry",
"//vendor:github.com/Azure/go-autorest/autorest/azure",
"//vendor:github.com/golang/glog",
"//vendor:github.com/spf13/pflag",
"//vendor:gopkg.in/yaml.v2",
],
)
go_test(
name = "go_default_test",
srcs = ["azure_credentials_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//vendor:github.com/Azure/azure-sdk-for-go/arm/containerregistry",
"//vendor:github.com/Azure/go-autorest/autorest/to",
],
)
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package azure
import (
"fmt"
"io/ioutil"
"time"
yaml "gopkg.in/yaml.v2"
"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
azureapi "github.com/Azure/go-autorest/autorest/azure"
"github.com/golang/glog"
"github.com/spf13/pflag"
"k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
"k8s.io/kubernetes/pkg/credentialprovider"
)
var flagConfigFile = pflag.String("azure-container-registry-config", "",
"Path to the file container Azure container registry configuration information.")
const dummyRegistryEmail = "name@contoso.com"
// init registers the various means by which credentials may
// be resolved on Azure.
func init() {
credentialprovider.RegisterCredentialProvider("azure",
&credentialprovider.CachingDockerConfigProvider{
Provider: NewACRProvider(flagConfigFile),
Lifetime: 1 * time.Minute,
})
}
type RegistriesClient interface {
List() (containerregistry.RegistryListResult, error)
}
func NewACRProvider(configFile *string) credentialprovider.DockerConfigProvider {
return &acrProvider{
file: configFile,
}
}
type acrProvider struct {
file *string
config azure.Config
environment azureapi.Environment
registryClient RegistriesClient
}
func (a *acrProvider) loadConfig(contents []byte) error {
err := yaml.Unmarshal(contents, &a.config)
if err != nil {
return err
}
if a.config.Cloud == "" {
a.environment = azureapi.PublicCloud
} else {
a.environment, err = azureapi.EnvironmentFromName(a.config.Cloud)
if err != nil {
return err
}
}
return nil
}
func (a *acrProvider) Enabled() bool {
if a.file == nil || len(*a.file) == 0 {
glog.V(5).Infof("Azure config unspecified, disabling")
return false
}
contents, err := ioutil.ReadFile(*a.file)
if err != nil {
glog.Errorf("Failed to load azure credential file: %v", err)
return false
}
if err := a.loadConfig(contents); err != nil {
glog.Errorf("Failed to parse azure credential file: %v", err)
return false
}
oauthConfig, err := a.environment.OAuthConfigForTenant(a.config.TenantID)
if err != nil {
glog.Errorf("Failed to get oauth config: %v", err)
return false
}
servicePrincipalToken, err := azureapi.NewServicePrincipalToken(
*oauthConfig,
a.config.AADClientID,
a.config.AADClientSecret,
a.environment.ServiceManagementEndpoint)
if err != nil {
glog.Errorf("Failed to create service principal token: %v", err)
return false
}
registryClient := containerregistry.NewRegistriesClient(a.config.SubscriptionID)
registryClient.BaseURI = a.environment.ResourceManagerEndpoint
registryClient.Authorizer = servicePrincipalToken
a.registryClient = registryClient
return true
}
func (a *acrProvider) Provide() credentialprovider.DockerConfig {
cfg := credentialprovider.DockerConfig{}
entry := credentialprovider.DockerConfigEntry{
Username: a.config.AADClientID,
Password: a.config.AADClientSecret,
Email: dummyRegistryEmail,
}
res, err := a.registryClient.List()
if err != nil {
glog.Errorf("Failed to list registries: %v", err)
return cfg
}
for ix := range *res.Value {
// TODO: I don't think this will work for national clouds
cfg[fmt.Sprintf("%s.azurecr.io", *(*res.Value)[ix].Name)] = entry
}
return cfg
}
func (a *acrProvider) LazyProvide() *credentialprovider.DockerConfigEntry {
return nil
}
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package azure
import (
"testing"
"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
"github.com/Azure/go-autorest/autorest/to"
)
type fakeClient struct {
results containerregistry.RegistryListResult
}
func (f *fakeClient) List() (containerregistry.RegistryListResult, error) {
return f.results, nil
}
func Test(t *testing.T) {
configStr := `
{
"aadClientId": "foo",
"aadClientSecret": "bar"
}`
result := containerregistry.RegistryListResult{
Value: &[]containerregistry.Registry{
{
Name: to.StringPtr("foo"),
},
{
Name: to.StringPtr("bar"),
},
{
Name: to.StringPtr("baz"),
},
},
}
fakeClient := &fakeClient{
results: result,
}
provider := &acrProvider{
registryClient: fakeClient,
}
provider.loadConfig([]byte(configStr))
creds := provider.Provide()
if len(creds) != len(*result.Value) {
t.Errorf("Unexpected list: %v, expected length %d", creds, len(*result.Value))
}
for _, cred := range creds {
if cred.Username != "foo" {
t.Errorf("expected 'foo' for username, saw: %v", cred.Username)
}
if cred.Password != "bar" {
t.Errorf("expected 'bar' for password, saw: %v", cred.Username)
}
}
for _, val := range *result.Value {
registryName := *val.Name + ".azurecr.io"
if _, found := creds[registryName]; !found {
t.Errorf("Missing expected registry: %s", registryName)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment