mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-11 13:57:36 -08:00
{discovery,remote_write}/azure: Support default SDK authentication (#13099)
* discovery/azure: Offer default SDK authentication Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
parent
5ed21c0d76
commit
302e151de8
|
@ -1840,7 +1840,7 @@ var expectedErrors = []struct {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filename: "azure_authentication_method.bad.yml",
|
filename: "azure_authentication_method.bad.yml",
|
||||||
errMsg: "unknown authentication_type \"invalid\". Supported types are \"OAuth\" or \"ManagedIdentity\"",
|
errMsg: "unknown authentication_type \"invalid\". Supported types are \"OAuth\", \"ManagedIdentity\" or \"SDK\"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filename: "azure_bearertoken_basicauth.bad.yml",
|
filename: "azure_bearertoken_basicauth.bad.yml",
|
||||||
|
|
|
@ -65,6 +65,7 @@ const (
|
||||||
azureLabelMachineSize = azureLabel + "machine_size"
|
azureLabelMachineSize = azureLabel + "machine_size"
|
||||||
|
|
||||||
authMethodOAuth = "OAuth"
|
authMethodOAuth = "OAuth"
|
||||||
|
authMethodSDK = "SDK"
|
||||||
authMethodManagedIdentity = "ManagedIdentity"
|
authMethodManagedIdentity = "ManagedIdentity"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -164,8 +165,8 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.AuthenticationMethod != authMethodOAuth && c.AuthenticationMethod != authMethodManagedIdentity {
|
if c.AuthenticationMethod != authMethodOAuth && c.AuthenticationMethod != authMethodManagedIdentity && c.AuthenticationMethod != authMethodSDK {
|
||||||
return fmt.Errorf("unknown authentication_type %q. Supported types are %q or %q", c.AuthenticationMethod, authMethodOAuth, authMethodManagedIdentity)
|
return fmt.Errorf("unknown authentication_type %q. Supported types are %q, %q or %q", c.AuthenticationMethod, authMethodOAuth, authMethodManagedIdentity, authMethodSDK)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.HTTPClientConfig.Validate()
|
return c.HTTPClientConfig.Validate()
|
||||||
|
@ -294,6 +295,16 @@ func newCredential(cfg SDConfig, policyClientOptions policy.ClientOptions) (azco
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
credential = azcore.TokenCredential(secretCredential)
|
credential = azcore.TokenCredential(secretCredential)
|
||||||
|
case authMethodSDK:
|
||||||
|
options := &azidentity.DefaultAzureCredentialOptions{ClientOptions: policyClientOptions}
|
||||||
|
if len(cfg.TenantID) != 0 {
|
||||||
|
options.TenantID = cfg.TenantID
|
||||||
|
}
|
||||||
|
sdkCredential, err := azidentity.NewDefaultAzureCredential(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
credential = azcore.TokenCredential(sdkCredential)
|
||||||
}
|
}
|
||||||
return credential, nil
|
return credential, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -600,8 +600,10 @@ See below for the configuration options for Azure discovery:
|
||||||
# The Azure environment.
|
# The Azure environment.
|
||||||
[ environment: <string> | default = AzurePublicCloud ]
|
[ environment: <string> | default = AzurePublicCloud ]
|
||||||
|
|
||||||
# The authentication method, either OAuth or ManagedIdentity.
|
# The authentication method, either OAuth, ManagedIdentity or SDK.
|
||||||
# See https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
|
# See https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview
|
||||||
|
# SDK authentication method uses environment variables by default.
|
||||||
|
# See https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication
|
||||||
[ authentication_method: <string> | default = OAuth]
|
[ authentication_method: <string> | default = OAuth]
|
||||||
# The subscription ID. Always required.
|
# The subscription ID. Always required.
|
||||||
subscription_id: <string>
|
subscription_id: <string>
|
||||||
|
@ -3619,6 +3621,11 @@ azuread:
|
||||||
[ client_secret: <string> ]
|
[ client_secret: <string> ]
|
||||||
[ tenant_id: <string> ] ]
|
[ tenant_id: <string> ] ]
|
||||||
|
|
||||||
|
# Azure SDK auth.
|
||||||
|
# See https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication
|
||||||
|
[ sdk:
|
||||||
|
[ tenant_id: <string> ] ]
|
||||||
|
|
||||||
# Configures the remote write request's TLS settings.
|
# Configures the remote write request's TLS settings.
|
||||||
tls_config:
|
tls_config:
|
||||||
[ <tls_config> ]
|
[ <tls_config> ]
|
||||||
|
|
|
@ -61,6 +61,12 @@ type OAuthConfig struct {
|
||||||
TenantID string `yaml:"tenant_id,omitempty"`
|
TenantID string `yaml:"tenant_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SDKConfig is used to store azure SDK config values.
|
||||||
|
type SDKConfig struct {
|
||||||
|
// TenantID is the tenantId of the azure active directory application that is being used to authenticate.
|
||||||
|
TenantID string `yaml:"tenant_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// AzureADConfig is used to store the config values.
|
// AzureADConfig is used to store the config values.
|
||||||
type AzureADConfig struct { //nolint:revive // exported.
|
type AzureADConfig struct { //nolint:revive // exported.
|
||||||
// ManagedIdentity is the managed identity that is being used to authenticate.
|
// ManagedIdentity is the managed identity that is being used to authenticate.
|
||||||
|
@ -69,6 +75,9 @@ type AzureADConfig struct { //nolint:revive // exported.
|
||||||
// OAuth is the oauth config that is being used to authenticate.
|
// OAuth is the oauth config that is being used to authenticate.
|
||||||
OAuth *OAuthConfig `yaml:"oauth,omitempty"`
|
OAuth *OAuthConfig `yaml:"oauth,omitempty"`
|
||||||
|
|
||||||
|
// OAuth is the oauth config that is being used to authenticate.
|
||||||
|
SDK *SDKConfig `yaml:"sdk,omitempty"`
|
||||||
|
|
||||||
// Cloud is the Azure cloud in which the service is running. Example: AzurePublic/AzureGovernment/AzureChina.
|
// Cloud is the Azure cloud in which the service is running. Example: AzurePublic/AzureGovernment/AzureChina.
|
||||||
Cloud string `yaml:"cloud,omitempty"`
|
Cloud string `yaml:"cloud,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -102,14 +111,22 @@ func (c *AzureADConfig) Validate() error {
|
||||||
return fmt.Errorf("must provide a cloud in the Azure AD config")
|
return fmt.Errorf("must provide a cloud in the Azure AD config")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ManagedIdentity == nil && c.OAuth == nil {
|
if c.ManagedIdentity == nil && c.OAuth == nil && c.SDK == nil {
|
||||||
return fmt.Errorf("must provide an Azure Managed Identity or Azure OAuth in the Azure AD config")
|
return fmt.Errorf("must provide an Azure Managed Identity, Azure OAuth or Azure SDK in the Azure AD config")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ManagedIdentity != nil && c.OAuth != nil {
|
if c.ManagedIdentity != nil && c.OAuth != nil {
|
||||||
return fmt.Errorf("cannot provide both Azure Managed Identity and Azure OAuth in the Azure AD config")
|
return fmt.Errorf("cannot provide both Azure Managed Identity and Azure OAuth in the Azure AD config")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.ManagedIdentity != nil && c.SDK != nil {
|
||||||
|
return fmt.Errorf("cannot provide both Azure Managed Identity and Azure SDK in the Azure AD config")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.OAuth != nil && c.SDK != nil {
|
||||||
|
return fmt.Errorf("cannot provide both Azure OAuth and Azure SDK in the Azure AD config")
|
||||||
|
}
|
||||||
|
|
||||||
if c.ManagedIdentity != nil {
|
if c.ManagedIdentity != nil {
|
||||||
if c.ManagedIdentity.ClientID == "" {
|
if c.ManagedIdentity.ClientID == "" {
|
||||||
return fmt.Errorf("must provide an Azure Managed Identity client_id in the Azure AD config")
|
return fmt.Errorf("must provide an Azure Managed Identity client_id in the Azure AD config")
|
||||||
|
@ -143,6 +160,17 @@ func (c *AzureADConfig) Validate() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.SDK != nil {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if c.SDK.TenantID != "" {
|
||||||
|
_, err = regexp.MatchString("^[0-9a-zA-Z-.]+$", c.SDK.TenantID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("the provided Azure OAuth tenant_id is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,6 +253,16 @@ func newTokenCredential(cfg *AzureADConfig) (azcore.TokenCredential, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.SDK != nil {
|
||||||
|
sdkConfig := &SDKConfig{
|
||||||
|
TenantID: cfg.SDK.TenantID,
|
||||||
|
}
|
||||||
|
cred, err = newSDKTokenCredential(clientOpts, sdkConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cred, nil
|
return cred, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +279,12 @@ func newOAuthTokenCredential(clientOpts *azcore.ClientOptions, oAuthConfig *OAut
|
||||||
return azidentity.NewClientSecretCredential(oAuthConfig.TenantID, oAuthConfig.ClientID, oAuthConfig.ClientSecret, opts)
|
return azidentity.NewClientSecretCredential(oAuthConfig.TenantID, oAuthConfig.ClientID, oAuthConfig.ClientSecret, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newSDKTokenCredential returns new SDK token credential.
|
||||||
|
func newSDKTokenCredential(clientOpts *azcore.ClientOptions, sdkConfig *SDKConfig) (azcore.TokenCredential, error) {
|
||||||
|
opts := &azidentity.DefaultAzureCredentialOptions{ClientOptions: *clientOpts, TenantID: sdkConfig.TenantID}
|
||||||
|
return azidentity.NewDefaultAzureCredential(opts)
|
||||||
|
}
|
||||||
|
|
||||||
// newTokenProvider helps to fetch accessToken for different types of credential. This also takes care of
|
// newTokenProvider helps to fetch accessToken for different types of credential. This also takes care of
|
||||||
// refreshing the accessToken before expiry. This accessToken is attached to the Authorization header while making requests.
|
// refreshing the accessToken before expiry. This accessToken is attached to the Authorization header while making requests.
|
||||||
func newTokenProvider(cfg *AzureADConfig, cred azcore.TokenCredential) (*tokenProvider, error) {
|
func newTokenProvider(cfg *AzureADConfig, cred azcore.TokenCredential) (*tokenProvider, error) {
|
||||||
|
|
|
@ -39,7 +39,7 @@ const (
|
||||||
testTokenString = "testTokenString"
|
testTokenString = "testTokenString"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testTokenExpiry = time.Now().Add(5 * time.Second)
|
func testTokenExpiry() time.Time { return time.Now().Add(5 * time.Second) }
|
||||||
|
|
||||||
type AzureAdTestSuite struct {
|
type AzureAdTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
|
@ -94,7 +94,7 @@ func (ad *AzureAdTestSuite) TestAzureAdRoundTripper() {
|
||||||
|
|
||||||
testToken := &azcore.AccessToken{
|
testToken := &azcore.AccessToken{
|
||||||
Token: testTokenString,
|
Token: testTokenString,
|
||||||
ExpiresOn: testTokenExpiry,
|
ExpiresOn: testTokenExpiry(),
|
||||||
}
|
}
|
||||||
|
|
||||||
ad.mockCredential.On("GetToken", mock.Anything, mock.Anything).Return(*testToken, nil)
|
ad.mockCredential.On("GetToken", mock.Anything, mock.Anything).Return(*testToken, nil)
|
||||||
|
@ -145,7 +145,7 @@ func TestAzureAdConfig(t *testing.T) {
|
||||||
// Missing managedidentiy or oauth field.
|
// Missing managedidentiy or oauth field.
|
||||||
{
|
{
|
||||||
filename: "testdata/azuread_bad_configmissing.yaml",
|
filename: "testdata/azuread_bad_configmissing.yaml",
|
||||||
err: "must provide an Azure Managed Identity or Azure OAuth in the Azure AD config",
|
err: "must provide an Azure Managed Identity, Azure OAuth or Azure SDK in the Azure AD config",
|
||||||
},
|
},
|
||||||
// Invalid managedidentity client id.
|
// Invalid managedidentity client id.
|
||||||
{
|
{
|
||||||
|
@ -162,6 +162,11 @@ func TestAzureAdConfig(t *testing.T) {
|
||||||
filename: "testdata/azuread_bad_twoconfig.yaml",
|
filename: "testdata/azuread_bad_twoconfig.yaml",
|
||||||
err: "cannot provide both Azure Managed Identity and Azure OAuth in the Azure AD config",
|
err: "cannot provide both Azure Managed Identity and Azure OAuth in the Azure AD config",
|
||||||
},
|
},
|
||||||
|
// Invalid config when both sdk and oauth is provided.
|
||||||
|
{
|
||||||
|
filename: "testdata/azuread_bad_oauthsdkconfig.yaml",
|
||||||
|
err: "cannot provide both Azure OAuth and Azure SDK in the Azure AD config",
|
||||||
|
},
|
||||||
// Valid config with missing optionally cloud field.
|
// Valid config with missing optionally cloud field.
|
||||||
{
|
{
|
||||||
filename: "testdata/azuread_good_cloudmissing.yaml",
|
filename: "testdata/azuread_good_cloudmissing.yaml",
|
||||||
|
@ -174,6 +179,10 @@ func TestAzureAdConfig(t *testing.T) {
|
||||||
{
|
{
|
||||||
filename: "testdata/azuread_good_oauth.yaml",
|
filename: "testdata/azuread_good_oauth.yaml",
|
||||||
},
|
},
|
||||||
|
// Valid SDK config.
|
||||||
|
{
|
||||||
|
filename: "testdata/azuread_good_sdk.yaml",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
_, err := loadAzureAdConfig(c.filename)
|
_, err := loadAzureAdConfig(c.filename)
|
||||||
|
@ -232,6 +241,16 @@ func (s *TokenProviderTestSuite) TestNewTokenProvider() {
|
||||||
},
|
},
|
||||||
err: "Cloud is not specified or is incorrect: ",
|
err: "Cloud is not specified or is incorrect: ",
|
||||||
},
|
},
|
||||||
|
// Invalid tokenProvider for SDK.
|
||||||
|
{
|
||||||
|
cfg: &AzureADConfig{
|
||||||
|
Cloud: "PublicAzure",
|
||||||
|
SDK: &SDKConfig{
|
||||||
|
TenantID: dummyTenantID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: "Cloud is not specified or is incorrect: ",
|
||||||
|
},
|
||||||
// Valid tokenProvider for managedidentity.
|
// Valid tokenProvider for managedidentity.
|
||||||
{
|
{
|
||||||
cfg: &AzureADConfig{
|
cfg: &AzureADConfig{
|
||||||
|
@ -252,6 +271,15 @@ func (s *TokenProviderTestSuite) TestNewTokenProvider() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Valid tokenProvider for SDK.
|
||||||
|
{
|
||||||
|
cfg: &AzureADConfig{
|
||||||
|
Cloud: "AzurePublic",
|
||||||
|
SDK: &SDKConfig{
|
||||||
|
TenantID: dummyTenantID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
mockGetTokenCallCounter := 1
|
mockGetTokenCallCounter := 1
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
|
@ -264,11 +292,11 @@ func (s *TokenProviderTestSuite) TestNewTokenProvider() {
|
||||||
} else {
|
} else {
|
||||||
testToken := &azcore.AccessToken{
|
testToken := &azcore.AccessToken{
|
||||||
Token: testTokenString,
|
Token: testTokenString,
|
||||||
ExpiresOn: testTokenExpiry,
|
ExpiresOn: testTokenExpiry(),
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mockCredential.On("GetToken", mock.Anything, mock.Anything).Return(*testToken, nil).Once().
|
s.mockCredential.On("GetToken", mock.Anything, mock.Anything).Return(*testToken, nil).Once().
|
||||||
On("GetToken", mock.Anything, mock.Anything).Return(getToken(), nil)
|
On("GetToken", mock.Anything, mock.Anything).Return(getToken(), nil).Once()
|
||||||
|
|
||||||
actualTokenProvider, actualErr := newTokenProvider(c.cfg, s.mockCredential)
|
actualTokenProvider, actualErr := newTokenProvider(c.cfg, s.mockCredential)
|
||||||
|
|
||||||
|
|
7
storage/remote/azuread/testdata/azuread_bad_oauthsdkconfig.yaml
vendored
Normal file
7
storage/remote/azuread/testdata/azuread_bad_oauthsdkconfig.yaml
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
cloud: AzurePublic
|
||||||
|
oauth:
|
||||||
|
client_id: 00000000-0000-0000-0000-000000000000
|
||||||
|
client_secret: Cl1ent$ecret!
|
||||||
|
tenant_id: 00000000-a12b-3cd4-e56f-000000000000
|
||||||
|
sdk:
|
||||||
|
tenant_id: 00000000-a12b-3cd4-e56f-000000000000
|
3
storage/remote/azuread/testdata/azuread_good_sdk.yaml
vendored
Normal file
3
storage/remote/azuread/testdata/azuread_good_sdk.yaml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
cloud: AzurePublic
|
||||||
|
sdk:
|
||||||
|
tenant_id: 00000000-a12b-3cd4-e56f-000000000000
|
Loading…
Reference in a new issue