From b2a5ed229dac8bc1b0661ee1cde0bdc1aa6b6a4f Mon Sep 17 00:00:00 2001 From: Roman Sokolkov Date: Thu, 31 May 2018 14:45:34 +0200 Subject: [PATCH 1/2] Add Azure MSI support --- Gopkg.lock | 10 ++++----- Gopkg.toml | 2 +- docs/tutorials/azure.md | 10 +++++++++ provider/azure.go | 45 ++++++++++++++++++++++++++++------------- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 6c8d61e4b..e945b76b1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -24,8 +24,8 @@ "autorest/date", "autorest/to" ] - revision = "58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d" - version = "v8.0.0" + revision = "aa2a4534ab680e938d933870f58f23f77e0e208e" + version = "v10.9.0" [[projects]] name = "github.com/PuerkitoBio/purell" @@ -136,8 +136,8 @@ [[projects]] name = "github.com/dgrijalva/jwt-go" packages = ["."] - revision = "d2709f9f1f31ebcda9651b03077758c1f3a0018c" - version = "v3.0.0" + revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" + version = "v3.2.0" [[projects]] name = "github.com/digitalocean/godo" @@ -648,6 +648,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "d5deea43eb04e9ef3a6ecb3589b91c149e092505f66905baa01c67379776d231" + inputs-digest = "8d8869be9b64013c9670a0ba7f6a6eeaf31941fcfbdfc13f0fa57626e767c517" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 6042ecd50..89b13efea 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -10,7 +10,7 @@ ignored = ["github.com/kubernetes/repo-infra/kazel"] [[constraint]] name = "github.com/Azure/go-autorest" - version = "~8.0.0" + version = "~10.9.0" [[constraint]] name = "github.com/alecthomas/kingpin" diff --git a/docs/tutorials/azure.md b/docs/tutorials/azure.md index ffd8362e9..4d7cc1086 100644 --- a/docs/tutorials/azure.md +++ b/docs/tutorials/azure.md @@ -103,6 +103,16 @@ If the Kubernetes cluster is not hosted by Azure Container Services and you stil "resourceGroup": "MyDnsResourceGroup", } ``` +If [Azure Managed Service Identity (MSI)](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) is enabled for virtual machines, then there is no need to create separate service principal. The contents of `azure.json` should be similar to this: +``` +{ + "tenantId": "AzureAD tenant Id", + "subscriptionId": "Id", + "resourceGroup": "MyDnsResourceGroup", + "useManagedIdentityExtension": true, +} +``` + If you have all the information necessary: create a file called azure.json containing the json structure above and substitute the values. Otherwise create a service principal as previously shown before creating the Kubernetes secret. Then add the secret to the Kubernetes cluster before continuing: diff --git a/provider/azure.go b/provider/azure.go index e044761d0..022b22525 100644 --- a/provider/azure.go +++ b/provider/azure.go @@ -40,13 +40,14 @@ const ( ) type config struct { - Cloud string `json:"cloud" yaml:"cloud"` - TenantID string `json:"tenantId" yaml:"tenantId"` - SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"` - ResourceGroup string `json:"resourceGroup" yaml:"resourceGroup"` - Location string `json:"location" yaml:"location"` - ClientID string `json:"aadClientId" yaml:"aadClientId"` - ClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"` + Cloud string `json:"cloud" yaml:"cloud"` + TenantID string `json:"tenantId" yaml:"tenantId"` + SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"` + ResourceGroup string `json:"resourceGroup" yaml:"resourceGroup"` + Location string `json:"location" yaml:"location"` + ClientID string `json:"aadClientId" yaml:"aadClientId"` + ClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"` + UseManagedIdentityExtension bool `json:"useManagedIdentityExtension" yaml:"useManagedIdentityExtension"` } // ZonesClient is an interface of dns.ZoneClient that can be stubbed for testing. @@ -102,14 +103,30 @@ func NewAzureProvider(configFile string, domainFilter DomainFilter, zoneIDFilter } } - oauthConfig, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, cfg.TenantID) - if err != nil { - return nil, fmt.Errorf("failed to retrieve OAuth config: %v", err) - } + // Try to retrive token with MSI if enabled or with client ID and client secret. + var token *adal.ServicePrincipalToken + if cfg.UseManagedIdentityExtension { + log.Info("Using managed identity extension to retrieve access token for Azure API.") + msiEndpoint, err := adal.GetMSIVMEndpoint() + if err != nil { + return nil, fmt.Errorf("failed to get the managed service identity endpoint: %v", err) + } - token, err := adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, cfg.ClientSecret, environment.ResourceManagerEndpoint) - if err != nil { - return nil, fmt.Errorf("failed to create service principal token: %v", err) + token, err = adal.NewServicePrincipalTokenFromMSI(msiEndpoint, environment.ServiceManagementEndpoint) + if err != nil { + return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) + } + } else { + log.Info("Using client_id+client_secret to retrieve access token for Azure API.") + oauthConfig, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, cfg.TenantID) + if err != nil { + return nil, fmt.Errorf("failed to retrieve OAuth config: %v", err) + } + + token, err = adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, cfg.ClientSecret, environment.ResourceManagerEndpoint) + if err != nil { + return nil, fmt.Errorf("failed to create service principal token: %v", err) + } } zonesClient := dns.NewZonesClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID) From 9f668a5de7f8f7fdc9286cb02e48c31bc3a561f9 Mon Sep 17 00:00:00 2001 From: Roman Sokolkov Date: Fri, 1 Jun 2018 10:13:28 +0200 Subject: [PATCH 2/2] Move token code to separate function and add test --- provider/azure.go | 62 ++++++++++++++++++++++++++---------------- provider/azure_test.go | 16 +++++++++++ 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/provider/azure.go b/provider/azure.go index 022b22525..82077139e 100644 --- a/provider/azure.go +++ b/provider/azure.go @@ -103,30 +103,9 @@ func NewAzureProvider(configFile string, domainFilter DomainFilter, zoneIDFilter } } - // Try to retrive token with MSI if enabled or with client ID and client secret. - var token *adal.ServicePrincipalToken - if cfg.UseManagedIdentityExtension { - log.Info("Using managed identity extension to retrieve access token for Azure API.") - msiEndpoint, err := adal.GetMSIVMEndpoint() - if err != nil { - return nil, fmt.Errorf("failed to get the managed service identity endpoint: %v", err) - } - - token, err = adal.NewServicePrincipalTokenFromMSI(msiEndpoint, environment.ServiceManagementEndpoint) - if err != nil { - return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) - } - } else { - log.Info("Using client_id+client_secret to retrieve access token for Azure API.") - oauthConfig, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, cfg.TenantID) - if err != nil { - return nil, fmt.Errorf("failed to retrieve OAuth config: %v", err) - } - - token, err = adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, cfg.ClientSecret, environment.ResourceManagerEndpoint) - if err != nil { - return nil, fmt.Errorf("failed to create service principal token: %v", err) - } + token, err := getAccessToken(cfg, environment) + if err != nil { + return nil, fmt.Errorf("failed to get token: %v", err) } zonesClient := dns.NewZonesClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID) @@ -145,6 +124,41 @@ func NewAzureProvider(configFile string, domainFilter DomainFilter, zoneIDFilter return provider, nil } +// getAccessToken retrieves Azure API access token. +func getAccessToken(cfg config, environment azure.Environment) (*adal.ServicePrincipalToken, error) { + // Try to retrive token with MSI. + if cfg.UseManagedIdentityExtension { + log.Info("Using managed identity extension to retrieve access token for Azure API.") + msiEndpoint, err := adal.GetMSIVMEndpoint() + if err != nil { + return nil, fmt.Errorf("failed to get the managed service identity endpoint: %v", err) + } + + token, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, environment.ServiceManagementEndpoint) + if err != nil { + return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) + } + return token, nil + } + + // Try to retrieve token with service principal credentials. + if len(cfg.ClientID) > 0 && len(cfg.ClientSecret) > 0 { + log.Info("Using client_id+client_secret to retrieve access token for Azure API.") + oauthConfig, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, cfg.TenantID) + if err != nil { + return nil, fmt.Errorf("failed to retrieve OAuth config: %v", err) + } + + token, err := adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, cfg.ClientSecret, environment.ResourceManagerEndpoint) + if err != nil { + return nil, fmt.Errorf("failed to create service principal token: %v", err) + } + return token, nil + } + + return nil, fmt.Errorf("no credentials provided for Azure API") +} + // Records gets the current records. // // Returns the current records or an error if the operation failed. diff --git a/provider/azure_test.go b/provider/azure_test.go index bc0c601f5..bdae835e8 100644 --- a/provider/azure_test.go +++ b/provider/azure_test.go @@ -21,6 +21,7 @@ import ( "github.com/Azure/azure-sdk-for-go/arm/dns" "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/to" "github.com/kubernetes-incubator/external-dns/endpoint" @@ -302,3 +303,18 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordsClie t.Fatal(err) } } + +func TestAzureGetAccessToken(t *testing.T) { + env := azure.PublicCloud + cfg := config{ + ClientID: "", + ClientSecret: "", + TenantID: "", + UseManagedIdentityExtension: false, + } + + _, err := getAccessToken(cfg, env) + if err == nil { + t.Fatalf("expected to fail, but got no error") + } +}