2019-09-27 21:51:53 +00:00
/ *
Copyright 2017 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 auth
import (
"crypto/rsa"
"crypto/x509"
"fmt"
"io/ioutil"
"strings"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
"golang.org/x/crypto/pkcs12"
"k8s.io/klog"
)
var (
// ErrorNoAuth indicates that no credentials are provided.
ErrorNoAuth = fmt . Errorf ( "no credentials provided for Azure cloud provider" )
// ADFSIdentitySystem indicates value of tenantId for ADFS on Azure Stack.
ADFSIdentitySystem = "ADFS"
)
// AzureAuthConfig holds auth related part of cloud config
type AzureAuthConfig struct {
// The cloud environment identifier. Takes values from https://github.com/Azure/go-autorest/blob/ec5f4903f77ed9927ac95b19ab8e44ada64c1356/autorest/azure/environments.go#L13
Cloud string ` json:"cloud,omitempty" yaml:"cloud,omitempty" `
// The AAD Tenant ID for the Subscription that the cluster is deployed in
TenantID string ` json:"tenantId,omitempty" yaml:"tenantId,omitempty" `
// The ClientID for an AAD application with RBAC access to talk to Azure RM APIs
AADClientID string ` json:"aadClientId,omitempty" yaml:"aadClientId,omitempty" `
// The ClientSecret for an AAD application with RBAC access to talk to Azure RM APIs
AADClientSecret string ` json:"aadClientSecret,omitempty" yaml:"aadClientSecret,omitempty" `
// The path of a client certificate for an AAD application with RBAC access to talk to Azure RM APIs
AADClientCertPath string ` json:"aadClientCertPath,omitempty" yaml:"aadClientCertPath,omitempty" `
// The password of the client certificate for an AAD application with RBAC access to talk to Azure RM APIs
AADClientCertPassword string ` json:"aadClientCertPassword,omitempty" yaml:"aadClientCertPassword,omitempty" `
// Use managed service identity for the virtual machine to access Azure ARM APIs
UseManagedIdentityExtension bool ` json:"useManagedIdentityExtension,omitempty" yaml:"useManagedIdentityExtension,omitempty" `
// UserAssignedIdentityID contains the Client ID of the user assigned MSI which is assigned to the underlying VMs. If empty the user assigned identity is not used.
// More details of the user assigned identity can be found at: https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview
// For the user assigned identity specified here to be used, the UseManagedIdentityExtension has to be set to true.
UserAssignedIdentityID string ` json:"userAssignedIdentityID,omitempty" yaml:"userAssignedIdentityID,omitempty" `
// The ID of the Azure Subscription that the cluster is deployed in
SubscriptionID string ` json:"subscriptionId,omitempty" yaml:"subscriptionId,omitempty" `
// Identity system value for the deployment. This gets populate for Azure Stack case.
IdentitySystem string ` json:"identitySystem,omitempty" yaml:"identitySystem,omitempty" `
}
// GetServicePrincipalToken creates a new service principal token based on the configuration
func GetServicePrincipalToken ( config * AzureAuthConfig , env * azure . Environment ) ( * adal . ServicePrincipalToken , error ) {
var tenantID string
if strings . EqualFold ( config . IdentitySystem , ADFSIdentitySystem ) {
tenantID = "adfs"
} else {
tenantID = config . TenantID
}
if config . UseManagedIdentityExtension {
klog . V ( 2 ) . Infoln ( "azure: using managed identity extension to retrieve access token" )
msiEndpoint , err := adal . GetMSIVMEndpoint ( )
if err != nil {
return nil , fmt . Errorf ( "Getting the managed service identity endpoint: %v" , err )
}
if len ( config . UserAssignedIdentityID ) > 0 {
klog . V ( 4 ) . Info ( "azure: using User Assigned MSI ID to retrieve access token" )
return adal . NewServicePrincipalTokenFromMSIWithUserAssignedID ( msiEndpoint ,
env . ServiceManagementEndpoint ,
config . UserAssignedIdentityID )
}
klog . V ( 4 ) . Info ( "azure: using System Assigned MSI to retrieve access token" )
return adal . NewServicePrincipalTokenFromMSI (
msiEndpoint ,
env . ServiceManagementEndpoint )
}
2020-02-14 00:18:16 +00:00
oauthConfig , err := adal . NewOAuthConfig ( env . ActiveDirectoryEndpoint , tenantID )
2019-09-27 21:51:53 +00:00
if err != nil {
return nil , fmt . Errorf ( "creating the OAuth config: %v" , err )
}
if len ( config . AADClientSecret ) > 0 {
klog . V ( 2 ) . Infoln ( "azure: using client_id+client_secret to retrieve access token" )
return adal . NewServicePrincipalToken (
* oauthConfig ,
config . AADClientID ,
config . AADClientSecret ,
env . ServiceManagementEndpoint )
}
if len ( config . AADClientCertPath ) > 0 && len ( config . AADClientCertPassword ) > 0 {
klog . 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" , config . AADClientCertPath , err )
}
certificate , privateKey , err := decodePkcs12 ( certData , config . AADClientCertPassword )
if err != nil {
return nil , fmt . Errorf ( "decoding the client certificate: %v" , err )
}
return adal . NewServicePrincipalTokenFromCertificate (
* oauthConfig ,
config . AADClientID ,
certificate ,
privateKey ,
env . ServiceManagementEndpoint )
}
return nil , ErrorNoAuth
}
// ParseAzureEnvironment returns azure environment by name
func ParseAzureEnvironment ( cloudName string ) ( * azure . Environment , error ) {
var env azure . Environment
var err error
if cloudName == "" {
env = azure . PublicCloud
} else {
env , err = azure . EnvironmentFromName ( cloudName )
}
return & env , err
}
// 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
}