Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azure: msi: add managed identity field, logic #48854

Merged
merged 4 commits into from Jul 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions hack/.linted_packages
Expand Up @@ -205,6 +205,7 @@ pkg/controller/volume/attachdetach/util
pkg/conversion
pkg/conversion/queryparams
pkg/credentialprovider/aws
pkg/credentialprovider/azure
pkg/fieldpath
pkg/fields
pkg/hyperkube
Expand Down
90 changes: 59 additions & 31 deletions pkg/cloudprovider/providers/azure/azure.go
Expand Up @@ -107,6 +107,9 @@ type Config struct {

// Use instance metadata service where possible
UseInstanceMetadata bool `json:"useInstanceMetadata" yaml:"useInstanceMetadata"`

// Use managed service identity for the virtual machine to access Azure ARM APIs
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension"`
}

// Cloud holds the config and clients
Expand Down Expand Up @@ -145,62 +148,62 @@ func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.Private
return certificate, rsaPrivateKey, nil
}

// newServicePrincipalToken creates a new service principal token based on the configuration
func newServicePrincipalToken(az *Cloud) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(az.Environment.ActiveDirectoryEndpoint, az.TenantID)
// GetServicePrincipalToken creates a new service principal token based on the configuration
func GetServicePrincipalToken(config *Config, env *azure.Environment) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, config.TenantID)
if err != nil {
return nil, fmt.Errorf("creating the OAuth config: %v", err)
}

if len(az.AADClientSecret) > 0 {
if config.UseManagedIdentityExtension {
glog.V(2).Infoln("azure: using managed identity extension to retrieve access token")
return adal.NewServicePrincipalTokenFromMSI(
*oauthConfig,
env.ServiceManagementEndpoint)
}

if len(config.AADClientSecret) > 0 {
glog.V(2).Infoln("azure: using client_id+client_secret to retrieve access token")
return adal.NewServicePrincipalToken(
*oauthConfig,
az.AADClientID,
az.AADClientSecret,
az.Environment.ServiceManagementEndpoint)
} else if len(az.AADClientCertPath) > 0 && len(az.AADClientCertPassword) > 0 {
certData, err := ioutil.ReadFile(az.AADClientCertPath)
config.AADClientID,
config.AADClientSecret,
env.ServiceManagementEndpoint)
}

if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 {
glog.V(2).Infoln("azure: using jwt client_assertion (client_cert+client_private_key) to retrieve access token")
certData, err := ioutil.ReadFile(config.AADClientCertPath)
if err != nil {
return nil, fmt.Errorf("reading the client certificate from file %s: %v", az.AADClientCertPath, err)
return nil, fmt.Errorf("reading the client certificate from file %s: %v", config.AADClientCertPath, err)
}
certificate, privateKey, err := decodePkcs12(certData, az.AADClientCertPassword)
certificate, privateKey, err := decodePkcs12(certData, config.AADClientCertPassword)
if err != nil {
return nil, fmt.Errorf("decoding the client certificate: %v", err)
}
return adal.NewServicePrincipalTokenFromCertificate(
*oauthConfig,
az.AADClientID,
config.AADClientID,
certificate,
privateKey,
az.Environment.ServiceManagementEndpoint)
} else {
return nil, fmt.Errorf("No credentials provided for AAD application %s", az.AADClientID)
env.ServiceManagementEndpoint)
}

return nil, fmt.Errorf("No credentials provided for AAD application %s", config.AADClientID)
}

// NewCloud returns a Cloud with initialized clients
func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) {
var az Cloud

configContents, err := ioutil.ReadAll(configReader)
config, env, err := ParseConfig(configReader)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(configContents, &az)
if err != nil {
return nil, err
}

if az.Cloud == "" {
az.Environment = azure.PublicCloud
} else {
az.Environment, err = azure.EnvironmentFromName(az.Cloud)
if err != nil {
return nil, err
}
az := Cloud{
Config: *config,
Environment: *env,
}

servicePrincipalToken, err := newServicePrincipalToken(&az)
servicePrincipalToken, err := GetServicePrincipalToken(config, env)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -307,6 +310,31 @@ func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) {
return &az, nil
}

// ParseConfig returns a parsed configuration and azure.Environment for an Azure cloudprovider config file
func ParseConfig(configReader io.Reader) (*Config, *azure.Environment, error) {
var config Config

configContents, err := ioutil.ReadAll(configReader)
if err != nil {
return nil, nil, err
}
err = yaml.Unmarshal(configContents, &config)
if err != nil {
return nil, nil, err
}

var env azure.Environment
if config.Cloud == "" {
env = azure.PublicCloud
} else {
env, err = azure.EnvironmentFromName(config.Cloud)
if err != nil {
return nil, nil, err
}
}
return &config, &env, nil
}

// Initialize passes a Kubernetes clientBuilder interface to the cloud provider
func (az *Cloud) Initialize(clientBuilder controller.ControllerClientBuilder) {}

Expand Down
2 changes: 0 additions & 2 deletions pkg/credentialprovider/azure/BUILD
Expand Up @@ -17,11 +17,9 @@ go_library(
"//pkg/credentialprovider:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/arm/containerregistry:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/adal:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
],
)

Expand Down
48 changes: 17 additions & 31 deletions pkg/credentialprovider/azure/azure_credentials.go
Expand Up @@ -17,14 +17,12 @@ limitations under the License.
package azure

import (
"io/ioutil"
"io"
"os"
"time"

yaml "gopkg.in/yaml.v2"

"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
azureapi "github.com/Azure/go-autorest/autorest/azure"
"github.com/golang/glog"
"github.com/spf13/pflag"
Expand All @@ -47,10 +45,12 @@ func init() {
})
}

// RegistriesClient is a testable interface for the ACR client List operation.
type RegistriesClient interface {
List() (containerregistry.RegistryListResult, error)
}

// NewACRProvider parses the specified configFile and returns a DockerConfigProvider
func NewACRProvider(configFile *string) credentialprovider.DockerConfigProvider {
return &acrProvider{
file: configFile,
Expand All @@ -59,24 +59,16 @@ func NewACRProvider(configFile *string) credentialprovider.DockerConfigProvider

type acrProvider struct {
file *string
config azure.Config
environment azureapi.Environment
config *azure.Config
environment *azureapi.Environment
registryClient RegistriesClient
}

func (a *acrProvider) loadConfig(contents []byte) error {
err := yaml.Unmarshal(contents, &a.config)
func (a *acrProvider) loadConfig(rdr io.Reader) error {
var err error
a.config, a.environment, err = azure.ParseConfig(rdr)
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
}
glog.Errorf("Failed to load azure credential file: %v", err)
}
return nil
}
Expand All @@ -86,27 +78,21 @@ func (a *acrProvider) Enabled() bool {
glog.V(5).Infof("Azure config unspecified, disabling")
return false
}
contents, err := ioutil.ReadFile(*a.file)

f, err := os.Open(*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)
glog.Errorf("Failed to load config from file: %s", *a.file)
return false
}
defer f.Close()

oauthConfig, err := adal.NewOAuthConfig(a.environment.ActiveDirectoryEndpoint, a.config.TenantID)
err = a.loadConfig(f)
if err != nil {
glog.Errorf("Failed to get oauth config: %v", err)
glog.Errorf("Failed to load config from file: %s", *a.file)
return false
}

servicePrincipalToken, err := adal.NewServicePrincipalToken(
*oauthConfig,
a.config.AADClientID,
a.config.AADClientSecret,
a.environment.ServiceManagementEndpoint)
servicePrincipalToken, err := azure.GetServicePrincipalToken(a.config, a.environment)
if err != nil {
glog.Errorf("Failed to create service principal token: %v", err)
return false
Expand Down
3 changes: 2 additions & 1 deletion pkg/credentialprovider/azure/azure_credentials_test.go
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package azure

import (
"bytes"
"testing"

"github.com/Azure/azure-sdk-for-go/arm/containerregistry"
Expand Down Expand Up @@ -66,7 +67,7 @@ func Test(t *testing.T) {
provider := &acrProvider{
registryClient: fakeClient,
}
provider.loadConfig([]byte(configStr))
provider.loadConfig(bytes.NewBufferString(configStr))

creds := provider.Provide()

Expand Down