remote_write azure auth : add workload identity support (#16788)

* initial changes

Signed-off-by: Kaveesh Dubey <kadubey@microsoft.com>

* .

Signed-off-by: Kaveesh Dubey <kadubey@microsoft.com>

* fix comments

Signed-off-by: Kaveesh Dubey <kadubey@microsoft.com>

* fix tenantid test

Signed-off-by: Kaveesh Dubey <kadubey@microsoft.com>

* style

Signed-off-by: Kaveesh Dubey <kadubey@microsoft.com>

* Update storage/remote/azuread/azuread.go

Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com>
Signed-off-by: bragi92 <kadubey@microsoft.com>

* Update storage/remote/azuread/azuread.go

Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com>
Signed-off-by: bragi92 <kadubey@microsoft.com>

* Update storage/remote/azuread/azuread.go

Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com>
Signed-off-by: bragi92 <kadubey@microsoft.com>

* Update storage/remote/azuread/azuread.go

Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com>
Signed-off-by: bragi92 <kadubey@microsoft.com>

* Update storage/remote/azuread/azuread.go

Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com>
Signed-off-by: bragi92 <kadubey@microsoft.com>

* Update storage/remote/azuread/azuread.go

Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com>
Signed-off-by: bragi92 <kadubey@microsoft.com>

* Update storage/remote/azuread/azuread.go

Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com>
Signed-off-by: bragi92 <kadubey@microsoft.com>

* pr feedback

Signed-off-by: Kaveesh Dubey <kadubey@microsoft.com>

---------

Signed-off-by: Kaveesh Dubey <kadubey@microsoft.com>
Signed-off-by: bragi92 <kadubey@microsoft.com>
Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com>
This commit is contained in:
bragi92 2025-08-25 23:14:47 -07:00 committed by GitHub
parent 7cf585527f
commit 20580b6ba8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 177 additions and 25 deletions

View File

@ -3021,6 +3021,12 @@ azuread:
[ managed_identity:
[ client_id: <string> ] ]
# Azure Workload Identity.
[ workload_identity:
client_id: <string>
tenant_id: <string>
[ token_file_path: <string> | default = "/var/run/secrets/azure/tokens/azure-identity-token" ] ]
# Azure OAuth.
[ oauth:
[ client_id: <string> ]

View File

@ -43,12 +43,32 @@ const (
IngestionPublicAudience = "https://monitor.azure.com//.default"
)
const (
// DefaultWorkloadIdentityTokenPath is the default path where the Azure Workload Identity
// webhook puts the service account token on Azure environments. See <azure docs link>.
DefaultWorkloadIdentityTokenPath = "/var/run/secrets/azure/tokens/azure-identity-token"
)
// ManagedIdentityConfig is used to store managed identity config values.
type ManagedIdentityConfig struct {
// ClientID is the clientId of the managed identity that is being used to authenticate.
ClientID string `yaml:"client_id,omitempty"`
}
// WorkloadIdentityConfig is used to store workload identity config values.
type WorkloadIdentityConfig struct {
// ClientID is the clientId of the Microsoft Entra application or user-assigned managed identity.
ClientID string `yaml:"client_id,omitempty"`
// TenantID is the tenantId of the Microsoft Entra application or user-assigned managed identity.
// This should match the tenant ID where your application or managed identity is registered.
TenantID string `yaml:"tenant_id,omitempty"`
// TokenFilePath is the path to the token file provided by the Kubernetes service account projected volume.
// If not specified, it defaults to DefaultWorkloadIdentityTokenPath.
TokenFilePath string `yaml:"token_file_path,omitempty"`
}
// OAuthConfig is used to store azure oauth config values.
type OAuthConfig struct {
// ClientID is the clientId of the azure active directory application that is being used to authenticate.
@ -72,6 +92,9 @@ type AzureADConfig struct { //nolint:revive // exported.
// ManagedIdentity is the managed identity that is being used to authenticate.
ManagedIdentity *ManagedIdentityConfig `yaml:"managed_identity,omitempty"`
// WorkloadIdentity is the workload identity that is being used to authenticate.
WorkloadIdentity *WorkloadIdentityConfig `yaml:"workload_identity,omitempty"`
// OAuth is the oauth config that is being used to authenticate.
OAuth *OAuthConfig `yaml:"oauth,omitempty"`
@ -111,20 +134,25 @@ func (c *AzureADConfig) Validate() error {
return errors.New("must provide a cloud in the Azure AD config")
}
if c.ManagedIdentity == nil && c.OAuth == nil && c.SDK == nil {
return errors.New("must provide an Azure Managed Identity, Azure OAuth or Azure SDK in the Azure AD config")
authenticators := 0
if c.ManagedIdentity != nil {
authenticators++
}
if c.WorkloadIdentity != nil {
authenticators++
}
if c.OAuth != nil {
authenticators++
}
if c.SDK != nil {
authenticators++
}
if c.ManagedIdentity != nil && c.OAuth != nil {
return errors.New("cannot provide both Azure Managed Identity and Azure OAuth in the Azure AD config")
if authenticators == 0 {
return errors.New("must provide an Azure Managed Identity, Azure Workload Identity, Azure OAuth or Azure SDK in the Azure AD config")
}
if c.ManagedIdentity != nil && c.SDK != nil {
return errors.New("cannot provide both Azure Managed Identity and Azure SDK in the Azure AD config")
}
if c.OAuth != nil && c.SDK != nil {
return errors.New("cannot provide both Azure OAuth and Azure SDK in the Azure AD config")
if authenticators > 1 {
return errors.New("cannot provide multiple authentication methods in the Azure AD config")
}
if c.ManagedIdentity != nil {
@ -136,6 +164,26 @@ func (c *AzureADConfig) Validate() error {
}
}
if c.WorkloadIdentity != nil {
if c.WorkloadIdentity.ClientID == "" {
return errors.New("must provide an Azure Workload Identity client_id in the Azure AD config")
}
if c.WorkloadIdentity.TenantID == "" {
return errors.New("must provide an Azure Workload Identity tenant_id in the Azure AD config")
}
if _, err := uuid.Parse(c.WorkloadIdentity.ClientID); err != nil {
return errors.New("the provided Azure Workload Identity client_id is invalid")
}
if _, err := uuid.Parse(c.WorkloadIdentity.TenantID); err != nil {
return errors.New("the provided Azure Workload Identity tenant_id is invalid")
}
if c.WorkloadIdentity.TokenFilePath == "" {
c.WorkloadIdentity.TokenFilePath = DefaultWorkloadIdentityTokenPath
}
}
if c.OAuth != nil {
if c.OAuth.ClientID == "" {
return errors.New("must provide an Azure OAuth client_id in the Azure AD config")
@ -147,24 +195,18 @@ func (c *AzureADConfig) Validate() error {
return errors.New("must provide an Azure OAuth tenant_id in the Azure AD config")
}
var err error
_, err = uuid.Parse(c.OAuth.ClientID)
if err != nil {
if _, err := uuid.Parse(c.OAuth.ClientID); err != nil {
return errors.New("the provided Azure OAuth client_id is invalid")
}
_, err = regexp.MatchString("^[0-9a-zA-Z-.]+$", c.OAuth.TenantID)
if err != nil {
if _, err := regexp.MatchString("^[0-9a-zA-Z-.]+$", c.OAuth.TenantID); err != nil {
return errors.New("the provided Azure OAuth tenant_id is invalid")
}
}
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 errors.New("the provided Azure OAuth tenant_id is invalid")
if _, err := regexp.MatchString("^[0-9a-zA-Z-.]+$", c.SDK.TenantID); err != nil {
return errors.New("the provided Azure SDK tenant_id is invalid")
}
}
}
@ -217,7 +259,7 @@ func (rt *azureADRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
return rt.next.RoundTrip(req)
}
// newTokenCredential returns a TokenCredential of different kinds like Azure Managed Identity and Azure AD application.
// newTokenCredential returns a TokenCredential of different kinds like Azure Managed Identity, Workload Identity and Azure AD application.
func newTokenCredential(cfg *AzureADConfig) (azcore.TokenCredential, error) {
var cred azcore.TokenCredential
var err error
@ -239,6 +281,18 @@ func newTokenCredential(cfg *AzureADConfig) (azcore.TokenCredential, error) {
}
}
if cfg.WorkloadIdentity != nil {
workloadIdentityConfig := &WorkloadIdentityConfig{
ClientID: cfg.WorkloadIdentity.ClientID,
TenantID: cfg.WorkloadIdentity.TenantID,
TokenFilePath: cfg.WorkloadIdentity.TokenFilePath,
}
cred, err = newWorkloadIdentityTokenCredential(clientOpts, workloadIdentityConfig)
if err != nil {
return nil, err
}
}
if cfg.OAuth != nil {
oAuthConfig := &OAuthConfig{
ClientID: cfg.OAuth.ClientID,
@ -276,6 +330,21 @@ func newManagedIdentityTokenCredential(clientOpts *azcore.ClientOptions, managed
return azidentity.NewManagedIdentityCredential(opts)
}
// newWorkloadIdentityTokenCredential returns new Microsoft Entra Workload Identity token credential.
//
// For detailed setup instructions, see:
// https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/prometheus-metrics-enable-workload-identity
func newWorkloadIdentityTokenCredential(clientOpts *azcore.ClientOptions, workloadIdentityConfig *WorkloadIdentityConfig) (azcore.TokenCredential, error) {
opts := &azidentity.WorkloadIdentityCredentialOptions{
ClientOptions: *clientOpts,
ClientID: workloadIdentityConfig.ClientID,
TenantID: workloadIdentityConfig.TenantID,
TokenFilePath: workloadIdentityConfig.TokenFilePath,
}
return azidentity.NewWorkloadIdentityCredential(opts)
}
// newOAuthTokenCredential returns new OAuth token credential.
func newOAuthTokenCredential(clientOpts *azcore.ClientOptions, oAuthConfig *OAuthConfig) (azcore.TokenCredential, error) {
opts := &azidentity.ClientSecretCredentialOptions{ClientOptions: *clientOpts}

View File

@ -88,6 +88,17 @@ func (ad *AzureAdTestSuite) TestAzureAdRoundTripper() {
},
},
},
// AzureAd roundtripper with Workload Identity.
{
cfg: &AzureADConfig{
Cloud: "AzurePublic",
WorkloadIdentity: &WorkloadIdentityConfig{
ClientID: dummyClientID,
TenantID: dummyTenantID,
TokenFilePath: DefaultWorkloadIdentityTokenPath,
},
},
},
}
for _, c := range cases {
var gotReq *http.Request
@ -145,7 +156,7 @@ func TestAzureAdConfig(t *testing.T) {
// Missing managedidentity or oauth field.
{
filename: "testdata/azuread_bad_configmissing.yaml",
err: "must provide an Azure Managed Identity, Azure OAuth or Azure SDK in the Azure AD config",
err: "must provide an Azure Managed Identity, Azure Workload Identity, Azure OAuth or Azure SDK in the Azure AD config",
},
// Invalid managedidentity client id.
{
@ -160,12 +171,32 @@ func TestAzureAdConfig(t *testing.T) {
// Invalid config when both managedidentity and oauth is provided.
{
filename: "testdata/azuread_bad_twoconfig.yaml",
err: "cannot provide both Azure Managed Identity and Azure OAuth in the Azure AD config",
err: "cannot provide multiple authentication methods 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",
err: "cannot provide multiple authentication methods in the Azure AD config",
},
// Invalid workload identity client id.
{
filename: "testdata/azuread_bad_workloadidentity_invalidclientid.yaml",
err: "the provided Azure Workload Identity client_id is invalid",
},
// Invalid workload identity tenant id.
{
filename: "testdata/azuread_bad_workloadidentity_invalidtenantid.yaml",
err: "the provided Azure Workload Identity tenant_id is invalid",
},
// Missing workload identity client id.
{
filename: "testdata/azuread_bad_workloadidentity_missingclientid.yaml",
err: "must provide an Azure Workload Identity client_id in the Azure AD config",
},
// Missing workload identity tenant id.
{
filename: "testdata/azuread_bad_workloadidentity_missingtenantid.yaml",
err: "must provide an Azure Workload Identity tenant_id in the Azure AD config",
},
// Valid config with missing optionally cloud field.
{
@ -187,6 +218,10 @@ func TestAzureAdConfig(t *testing.T) {
{
filename: "testdata/azuread_good_sdk.yaml",
},
// Valid workload identity config.
{
filename: "testdata/azuread_good_workloadidentity.yaml",
},
}
for _, c := range cases {
_, err := loadAzureAdConfig(c.filename)
@ -255,6 +290,18 @@ func (s *TokenProviderTestSuite) TestNewTokenProvider() {
},
err: "Cloud is not specified or is incorrect: ",
},
// Invalid tokenProvider for workload identity.
{
cfg: &AzureADConfig{
Cloud: "PublicAzure",
WorkloadIdentity: &WorkloadIdentityConfig{
ClientID: dummyClientID,
TenantID: dummyTenantID,
TokenFilePath: DefaultWorkloadIdentityTokenPath,
},
},
err: "Cloud is not specified or is incorrect: ",
},
// Valid tokenProvider for managedidentity.
{
cfg: &AzureADConfig{
@ -284,6 +331,17 @@ func (s *TokenProviderTestSuite) TestNewTokenProvider() {
},
},
},
// Valid tokenProvider for workload identity.
{
cfg: &AzureADConfig{
Cloud: "AzurePublic",
WorkloadIdentity: &WorkloadIdentityConfig{
ClientID: dummyClientID,
TenantID: dummyTenantID,
TokenFilePath: DefaultWorkloadIdentityTokenPath,
},
},
},
}
mockGetTokenCallCounter := 1
for _, c := range cases {

View File

@ -0,0 +1,4 @@
workload_identity:
client_id: "invalidclientid"
tenant_id: "00000000-a12b-3cd4-e56f-000000000000"
cloud: "AzurePublic"

View File

@ -0,0 +1,4 @@
workload_identity:
client_id: "00000000-0000-0000-0000-000000000000"
tenant_id: "invalid-tenant-id-!"
cloud: "AzurePublic"

View File

@ -0,0 +1,3 @@
workload_identity:
tenant_id: "00000000-a12b-3cd4-e56f-000000000000"
cloud: "AzurePublic"

View File

@ -0,0 +1,3 @@
workload_identity:
client_id: "00000000-0000-0000-0000-000000000000"
cloud: "AzurePublic"

View File

@ -0,0 +1,5 @@
workload_identity:
client_id: "00000000-0000-0000-0000-000000000000"
tenant_id: "00000000-a12b-3cd4-e56f-000000000000"
token_file_path: "/var/run/secrets/azure/tokens/azure-identity-token"
cloud: "AzurePublic"