From c7d83b2b6a08048e1bfa046f9fd63125ae327e02 Mon Sep 17 00:00:00 2001 From: "Marcel D. Juhnke" Date: Wed, 19 Dec 2018 11:03:33 +0100 Subject: [PATCH] discovery: add support for Managed Identity authentication in Azure SD (#4590) Signed-off-by: Marcel Juhnke --- config/config_test.go | 19 +++-- .../azure_authentication_method.bad.yml | 4 + config/testdata/conf.good.yml | 1 + discovery/azure/azure.go | 81 +++++++++++++------ docs/configuration/configuration.md | 18 +++-- 5 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 config/testdata/azure_authentication_method.bad.yml diff --git a/config/config_test.go b/config/config_test.go index 3cf099ce7..650802d93 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -444,13 +444,14 @@ var expectedConf = &Config{ ServiceDiscoveryConfig: sd_config.ServiceDiscoveryConfig{ AzureSDConfigs: []*azure.SDConfig{ { - Environment: "AzurePublicCloud", - SubscriptionID: "11AAAA11-A11A-111A-A111-1111A1111A11", - TenantID: "BBBB222B-B2B2-2B22-B222-2BB2222BB2B2", - ClientID: "333333CC-3C33-3333-CCC3-33C3CCCCC33C", - ClientSecret: "mysecret", - RefreshInterval: model.Duration(5 * time.Minute), - Port: 9100, + Environment: "AzurePublicCloud", + SubscriptionID: "11AAAA11-A11A-111A-A111-1111A1111A11", + TenantID: "BBBB222B-B2B2-2B22-B222-2BB2222BB2B2", + ClientID: "333333CC-3C33-3333-CCC3-33C3CCCCC33C", + ClientSecret: "mysecret", + AuthenticationMethod: "OAuth", + RefreshInterval: model.Duration(5 * time.Minute), + Port: 9100, }, }, }, @@ -769,6 +770,10 @@ var expectedErrors = []struct { filename: "azure_tenant_id_missing.bad.yml", errMsg: "Azure SD configuration requires a tenant_id", }, + { + filename: "azure_authentication_method.bad.yml", + errMsg: "Unknown authentication_type \"invalid\". Supported types are \"OAuth\" or \"ManagedIdentity\"", + }, { filename: "empty_scrape_config.bad.yml", errMsg: "empty or null scrape config section", diff --git a/config/testdata/azure_authentication_method.bad.yml b/config/testdata/azure_authentication_method.bad.yml new file mode 100644 index 000000000..b05fc474a --- /dev/null +++ b/config/testdata/azure_authentication_method.bad.yml @@ -0,0 +1,4 @@ +scrape_configs: +- azure_sd_configs: + - authentication_method: invalid + subscription_id: 11AAAA11-A11A-111A-A111-1111A1111A11 diff --git a/config/testdata/conf.good.yml b/config/testdata/conf.good.yml index 5abadc212..2db750d10 100644 --- a/config/testdata/conf.good.yml +++ b/config/testdata/conf.good.yml @@ -196,6 +196,7 @@ scrape_configs: - job_name: service-azure azure_sd_configs: - environment: AzurePublicCloud + authentication_method: OAuth subscription_id: 11AAAA11-A11A-111A-A111-1111A1111A11 tenant_id: BBBB222B-B2B2-2B22-B222-2BB2222BB2B2 client_id: 333333CC-3C33-3333-CCC3-33C3CCCCC33C diff --git a/discovery/azure/azure.go b/discovery/azure/azure.go index 7c999382e..4164325d6 100644 --- a/discovery/azure/azure.go +++ b/discovery/azure/azure.go @@ -26,13 +26,11 @@ import ( "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/azure" - "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" config_util "github.com/prometheus/common/config" "github.com/prometheus/common/model" - "github.com/prometheus/prometheus/discovery/targetgroup" "github.com/prometheus/prometheus/util/strutil" ) @@ -47,6 +45,9 @@ const ( azureLabelMachinePrivateIP = azureLabel + "machine_private_ip" azureLabelMachineTag = azureLabel + "machine_tag_" azureLabelMachineScaleSet = azureLabel + "machine_scale_set" + + authMethodOAuth = "OAuth" + authMethodManagedIdentity = "ManagedIdentity" ) var ( @@ -63,21 +64,23 @@ var ( // DefaultSDConfig is the default Azure SD configuration. DefaultSDConfig = SDConfig{ - Port: 80, - RefreshInterval: model.Duration(5 * time.Minute), - Environment: azure.PublicCloud.Name, + Port: 80, + RefreshInterval: model.Duration(5 * time.Minute), + Environment: azure.PublicCloud.Name, + AuthenticationMethod: authMethodOAuth, } ) // SDConfig is the configuration for Azure based service discovery. type SDConfig struct { - Environment string `yaml:"environment,omitempty"` - Port int `yaml:"port"` - SubscriptionID string `yaml:"subscription_id"` - TenantID string `yaml:"tenant_id,omitempty"` - ClientID string `yaml:"client_id,omitempty"` - ClientSecret config_util.Secret `yaml:"client_secret,omitempty"` - RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` + Environment string `yaml:"environment,omitempty"` + Port int `yaml:"port"` + SubscriptionID string `yaml:"subscription_id"` + TenantID string `yaml:"tenant_id,omitempty"` + ClientID string `yaml:"client_id,omitempty"` + ClientSecret config_util.Secret `yaml:"client_secret,omitempty"` + RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` + AuthenticationMethod string `yaml:"authentication_method,omitempty"` } func validateAuthParam(param, name string) error { @@ -95,18 +98,27 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if err != nil { return err } + if err = validateAuthParam(c.SubscriptionID, "subscription_id"); err != nil { return err } - if err = validateAuthParam(c.TenantID, "tenant_id"); err != nil { - return err + + if c.AuthenticationMethod == authMethodOAuth { + if err = validateAuthParam(c.TenantID, "tenant_id"); err != nil { + return err + } + if err = validateAuthParam(c.ClientID, "client_id"); err != nil { + return err + } + if err = validateAuthParam(string(c.ClientSecret), "client_secret"); err != nil { + return err + } } - if err = validateAuthParam(c.ClientID, "client_id"); err != nil { - return err - } - if err = validateAuthParam(string(c.ClientSecret), "client_secret"); err != nil { - return err + + if c.AuthenticationMethod != authMethodOAuth && c.AuthenticationMethod != authMethodManagedIdentity { + return fmt.Errorf("Unknown authentication_type %q. Supported types are %q or %q", c.AuthenticationMethod, authMethodOAuth, authMethodManagedIdentity) } + return nil } @@ -186,13 +198,30 @@ func createAzureClient(cfg SDConfig) (azureClient, error) { resourceManagerEndpoint := env.ResourceManagerEndpoint var c azureClient - oauthConfig, err := adal.NewOAuthConfig(activeDirectoryEndpoint, cfg.TenantID) - if err != nil { - return azureClient{}, err - } - spt, err := adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, string(cfg.ClientSecret), resourceManagerEndpoint) - if err != nil { - return azureClient{}, err + + var spt *adal.ServicePrincipalToken + + switch cfg.AuthenticationMethod { + case authMethodManagedIdentity: + msiEndpoint, err := adal.GetMSIVMEndpoint() + if err != nil { + return azureClient{}, err + } + + spt, err = adal.NewServicePrincipalTokenFromMSI(msiEndpoint, resourceManagerEndpoint) + if err != nil { + return azureClient{}, err + } + case authMethodOAuth: + oauthConfig, err := adal.NewOAuthConfig(activeDirectoryEndpoint, cfg.TenantID) + if err != nil { + return azureClient{}, err + } + + spt, err = adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, string(cfg.ClientSecret), resourceManagerEndpoint) + if err != nil { + return azureClient{}, err + } } bearerAuthorizer := autorest.NewBearerAuthorizer(spt) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index fa1b1e483..da8a6accf 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -274,14 +274,18 @@ See below for the configuration options for Azure discovery: # The information to access the Azure API. # The Azure environment. [ environment: | default = AzurePublicCloud ] -# The subscription ID. + +# The authentication method, either OAuth or ManagedIdentity. +# See https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview +[ authentication_method: | default = OAuth] +# The subscription ID. Always required. subscription_id: -# The tenant ID. -tenant_id: -# The client ID. -client_id: -# The client secret. -client_secret: +# Optional tenant ID. Only required with authentication_method OAuth. +[ tenant_id: ] +# Optional client ID. Only required with authentication_method OAuth. +[ client_id: ] +# Optional client secret. Only required with authentication_method OAuth. +[ client_secret: ] # Refresh interval to re-read the instance list. [ refresh_interval: | default = 300s ]