From 5462d06ce302156994c95176d0291f5265547d72 Mon Sep 17 00:00:00 2001 From: Cosmin Cojocar Date: Thu, 8 Jun 2017 15:05:11 +0200 Subject: [PATCH] Add client cert authentication for Azure cloud provider --- pkg/cloudprovider/providers/azure/azure.go | 66 ++++++++++++++++--- .../providers/azure/azure_test.go | 10 +++ 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/pkg/cloudprovider/providers/azure/azure.go b/pkg/cloudprovider/providers/azure/azure.go index ca2e48b7e6..21e20566bc 100644 --- a/pkg/cloudprovider/providers/azure/azure.go +++ b/pkg/cloudprovider/providers/azure/azure.go @@ -17,6 +17,8 @@ limitations under the License. package azure import ( + "crypto/rsa" + "crypto/x509" "fmt" "io" "io/ioutil" @@ -34,6 +36,7 @@ import ( "github.com/Azure/go-autorest/autorest/azure" "github.com/ghodss/yaml" "github.com/golang/glog" + "golang.org/x/crypto/pkcs12" "k8s.io/apimachinery/pkg/util/wait" ) @@ -80,6 +83,10 @@ type Config struct { AADClientID string `json:"aadClientId" yaml:"aadClientId"` // The ClientSecret for an AAD application with RBAC access to talk to Azure RM APIs AADClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"` + // The path of a client certificate for an AAD application with RBAC access to talk to Azure RM APIs + AADClientCertPath string `json:"aadClientCertPath" yaml:"aadClientCertPath"` + // The password of the client certificate for an AAD application with RBAC access to talk to Azure RM APIs + AADClientCertPassword string `json:"aadClientCertPassword" yaml:"aadClientCertPassword"` // Enable exponential backoff to manage resource request retries CloudProviderBackoff bool `json:"cloudProviderBackoff" yaml:"cloudProviderBackoff"` // Backoff retry limit @@ -119,6 +126,54 @@ func init() { cloudprovider.RegisterCloudProvider(CloudProviderName, NewCloud) } +// decodePkcs12 decodes a PKCS#12 client certificate by extracting the public certificate and +// the private RSA key +func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) { + privateKey, certificate, err := pkcs12.Decode(pkcs, password) + if err != nil { + return nil, nil, fmt.Errorf("decoding the PKCS#12 client certificate: %v", err) + } + rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey) + if !isRsaKey { + return nil, nil, fmt.Errorf("PKCS#12 certificate must contain a RSA private key") + } + + return certificate, rsaPrivateKey, nil +} + +// newServicePrincipalToken creates a new service principal token based on the configuration +func newServicePrincipalToken(az *Cloud) (*azure.ServicePrincipalToken, error) { + oauthConfig, err := az.Environment.OAuthConfigForTenant(az.TenantID) + if err != nil { + return nil, fmt.Errorf("creating the OAuth config: %v", err) + } + + if len(az.AADClientSecret) > 0 { + return azure.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) + if err != nil { + return nil, fmt.Errorf("reading the client certificate from file %s: %v", az.AADClientCertPath, err) + } + certificate, privateKey, err := decodePkcs12(certData, az.AADClientSecret) + if err != nil { + return nil, fmt.Errorf("decoding the client certificate: %v", err) + } + return azure.NewServicePrincipalTokenFromCertificate( + *oauthConfig, + az.AADClientID, + certificate, + privateKey, + az.Environment.ServiceManagementEndpoint) + } else { + return nil, fmt.Errorf("No credentials provided for AAD application %s", az.AADClientID) + } +} + // NewCloud returns a Cloud with initialized clients func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) { var az Cloud @@ -141,16 +196,7 @@ func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) { } } - oauthConfig, err := az.Environment.OAuthConfigForTenant(az.TenantID) - if err != nil { - return nil, err - } - - servicePrincipalToken, err := azure.NewServicePrincipalToken( - *oauthConfig, - az.AADClientID, - az.AADClientSecret, - az.Environment.ServiceManagementEndpoint) + servicePrincipalToken, err := newServicePrincipalToken(&az) if err != nil { return nil, err } diff --git a/pkg/cloudprovider/providers/azure/azure_test.go b/pkg/cloudprovider/providers/azure/azure_test.go index 788be6451c..6c4e634f5b 100644 --- a/pkg/cloudprovider/providers/azure/azure_test.go +++ b/pkg/cloudprovider/providers/azure/azure_test.go @@ -585,6 +585,8 @@ func TestNewCloudFromJSON(t *testing.T) { "subscriptionId": "--subscription-id--", "aadClientId": "--aad-client-id--", "aadClientSecret": "--aad-client-secret--", + "aadClientCertPath": "--aad-client-cert-path--", + "aadClientCertPassword": "--aad-client-cert-password--", "resourceGroup": "--resource-group--", "location": "--location--", "subnetName": "--subnet-name--", @@ -625,6 +627,8 @@ tenantId: --tenant-id-- subscriptionId: --subscription-id-- aadClientId: --aad-client-id-- aadClientSecret: --aad-client-secret-- +aadClientCertPath: --aad-client-cert-path-- +aadClientCertPassword: --aad-client-cert-password-- resourceGroup: --resource-group-- location: --location-- subnetName: --subnet-name-- @@ -659,6 +663,12 @@ func validateConfig(t *testing.T, config string) { if azureCloud.AADClientSecret != "--aad-client-secret--" { t.Errorf("got incorrect value for AADClientSecret") } + if azureCloud.AADClientCertPath != "--aad-client-cert-path--" { + t.Errorf("got incorrect value for AADClientCertPath") + } + if azureCloud.AADClientCertPassword != "--aad-client-cert-password--" { + t.Errorf("got incorrect value for AADClientCertPassword") + } if azureCloud.ResourceGroup != "--resource-group--" { t.Errorf("got incorrect value for ResourceGroup") }