Merge pull request #47286 from cosmincojocar/client_cert_azure_cloud_provider

Automatic merge from submit-queue (batch tested with PRs 47286, 47729)

Add client certificate authentication to Azure cloud provider

This adds support for client cert authentication in Azure cloud provider. The certificate can be provided in PKCS #12 format with password protection. Not that this authentication will be active only when no client secret is configured.

cc @brendandburns @colemickens
This commit is contained in:
Kubernetes Submit Queue 2017-06-28 23:14:29 -07:00 committed by GitHub
commit db46e4f8e6
3 changed files with 75 additions and 13 deletions

View File

@ -42,6 +42,7 @@ go_library(
"//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/rubiojr/go-vhd/vhd:go_default_library",
"//vendor/golang.org/x/crypto/pkcs12:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",

View File

@ -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
}

View File

@ -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--",
@ -606,15 +608,20 @@ func TestNewCloudFromJSON(t *testing.T) {
// Test Backoff and Rate Limit defaults (json)
func TestCloudDefaultConfigFromJSON(t *testing.T) {
config := `{}`
config := `{
"aadClientId": "--aad-client-id--",
"aadClientSecret": "--aad-client-secret--"
}`
validateEmptyConfig(t, config)
}
// Test Backoff and Rate Limit defaults (yaml)
func TestCloudDefaultConfigFromYAML(t *testing.T) {
config := ``
config := `
aadClientId: --aad-client-id--
aadClientSecret: --aad-client-secret--
`
validateEmptyConfig(t, config)
}
@ -625,6 +632,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 +668,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")
}