Skip to content

Commit

Permalink
[receiver/azuremonitor] Add support for azure workload identity auth (o…
Browse files Browse the repository at this point in the history
…pen-telemetry#24543)

Enhances azuremonitor receiver authentication by using [azure workload
identity](https://azure.github.io/azure-workload-identity/docs/). I have
added a new config parameter `auth` to specify the used authentication
method (default set to service-principal) in order to possibly add more
methods in the future.

Fixes open-telemetry#24451 

**Testing:** Tested on my AKS
  • Loading branch information
cmergenthaler authored and jmsnll committed Nov 12, 2023
1 parent 6bbfcfb commit 7831cd1
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 66 deletions.
20 changes: 20 additions & 0 deletions .chloggen/azuremonitor-workload-identity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use this changelog template to create an entry for release notes.
# If your change doesn't affect end users, such as a test fix or a tooling change,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: receiver/azuremonitorreceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add support for authenticating using AD workload identity

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [24451]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
28 changes: 24 additions & 4 deletions receiver/azuremonitorreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,29 @@ This receiver scrapes Azure Monitor API for resources metrics.

The following settings are required:
- `subscription_id`
- `tenant_id`
- `client_id`
- `client_secret`

The following settings are optional:
- `auth` (default = service_principal): Specifies the used authentication method. Supported values are `service_principal`, `workload_identity`.
- `resource_groups` (default = none): Filter metrics for specific resource groups, not setting a value will scrape metrics for all resources in the subscription.
- `services` (default = none): Filter metrics for specific services, not setting a value will scrape metrics for all services integrated with Azure Monitor.
- `cache_resources` (default = 86400): List of resources will be cached for the provided amount of time in seconds.
- `cache_resources_definitions` (default = 86400): List of metrics definitions will be cached for the provided amount of time in seconds.
- `maximum_number_of_metrics_in_a_call` (default = 20): Maximum number of metrics to fetch in per API call, current limit in Azure is 20 (as of 03/27/2023).
- `initial_delay` (default = `1s`): defines how long this receiver waits before starting.

### Example Configuration
Authenticating using service principal requires following additional settings:
- `tenant_id`
- `client_id`
- `client_secret`

Authenticating using workload identities requires following additional settings:
- `tenant_id`
- `client_id`
- `federate_token_file`

### Example Configurations

Using Service Principal for authentication:
```yaml
receivers:
azuremonitor:
Expand All @@ -49,6 +58,17 @@ receivers:
initial_delay: 1s
```
Using Azure Workload Identity for authentication:
```yaml
receivers:
azuremonitor:
subscription_id: "${subscription_id}"
auth: "workload_identity"
tenant_id: "${env:AZURE_TENANT_ID}"
client_id: "${env:AZURE_CLIENT_ID}"
federated_token_file: "${env:AZURE_FEDERATED_TOKEN_FILE}"
```
## Metrics
Details about the metrics scraped by this receiver can be found in [Supported metrics with Azure Monitor](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-supported). This receiver adds the prefix "azure_" to all scraped metrics.
Expand Down
44 changes: 35 additions & 9 deletions receiver/azuremonitorreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package azuremonitorreceiver // import "github.com/open-telemetry/opentelemetry-

import (
"errors"
"fmt"

"go.opentelemetry.io/collector/receiver/scraperhelper"
"go.uber.org/multierr"
Expand All @@ -18,6 +19,7 @@ var (
errMissingSubscriptionID = errors.New(`SubscriptionID" is not specified in config`)
errMissingClientID = errors.New(`ClientID" is not specified in config`)
errMissingClientSecret = errors.New(`ClientSecret" is not specified in config`)
errMissingFedTokenFile = errors.New(`FederatedTokenFile is not specified in config`)

monitorServices = []string{
"Microsoft.EventGrid/eventSubscriptions",
Expand Down Expand Up @@ -224,9 +226,11 @@ type Config struct {
scraperhelper.ScraperControllerSettings `mapstructure:",squash"`
MetricsBuilderConfig metadata.MetricsBuilderConfig `mapstructure:",squash"`
SubscriptionID string `mapstructure:"subscription_id"`
Authentication string `mapstructure:"auth"`
TenantID string `mapstructure:"tenant_id"`
ClientID string `mapstructure:"client_id"`
ClientSecret string `mapstructure:"client_secret"`
FederatedTokenFile string `mapstructure:"federated_token_file"`
ResourceGroups []string `mapstructure:"resource_groups"`
Services []string `mapstructure:"services"`
CacheResources float64 `mapstructure:"cache_resources"`
Expand All @@ -235,22 +239,44 @@ type Config struct {
AppendTagsAsAttributes bool `mapstructure:"append_tags_as_attributes"`
}

const (
servicePrincipal = "service_principal"
workloadIdentity = "workload_identity"
)

// Validate validates the configuration by checking for missing or invalid fields
func (c Config) Validate() (err error) {
if c.TenantID == "" {
err = multierr.Append(err, errMissingTenantID)
}

if c.SubscriptionID == "" {
err = multierr.Append(err, errMissingSubscriptionID)
}

if c.ClientID == "" {
err = multierr.Append(err, errMissingClientID)
}
switch c.Authentication {
case servicePrincipal:
if c.TenantID == "" {
err = multierr.Append(err, errMissingTenantID)
}

if c.ClientID == "" {
err = multierr.Append(err, errMissingClientID)
}

if c.ClientSecret == "" {
err = multierr.Append(err, errMissingClientSecret)
}
case workloadIdentity:
if c.TenantID == "" {
err = multierr.Append(err, errMissingTenantID)
}

if c.ClientID == "" {
err = multierr.Append(err, errMissingClientID)
}

if c.ClientSecret == "" {
err = multierr.Append(err, errMissingClientSecret)
if c.FederatedTokenFile == "" {
err = multierr.Append(err, errMissingFedTokenFile)
}
default:
return fmt.Errorf("authentication %v is not supported. supported authentications include [%v,%v]", c.Authentication, servicePrincipal, workloadIdentity)
}

return
Expand Down
1 change: 1 addition & 0 deletions receiver/azuremonitorreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func createDefaultConfig() component.Config {
CacheResourcesDefinitions: 24 * 60 * 60,
MaximumNumberOfMetricsInACall: 20,
Services: monitorServices,
Authentication: servicePrincipal,
}
}

Expand Down
1 change: 1 addition & 0 deletions receiver/azuremonitorreceiver/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func TestNewFactory(t *testing.T) {
CacheResources: 24 * 60 * 60,
CacheResourcesDefinitions: 24 * 60 * 60,
MaximumNumberOfMetricsInACall: 20,
Authentication: servicePrincipal,
}

require.Equal(t, expectedCfg, factory.CreateDefaultConfig())
Expand Down
12 changes: 6 additions & 6 deletions receiver/azuremonitorreceiver/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azurem
go 1.20

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.8.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0
github.com/google/go-cmp v0.5.9
Expand All @@ -21,14 +21,14 @@ require (
)

require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
Expand All @@ -41,7 +41,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.86.0 // indirect
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/collector v0.86.0 // indirect
Expand Down
27 changes: 14 additions & 13 deletions receiver/azuremonitorreceiver/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 19 additions & 2 deletions receiver/azuremonitorreceiver/scraper.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func newScraper(conf *Config, settings receiver.CreateSettings) *azureScraper {
settings: settings.TelemetrySettings,
mb: metadata.NewMetricsBuilder(conf.MetricsBuilderConfig, settings),
azIDCredentialsFunc: azidentity.NewClientSecretCredential,
azIDWorkloadFunc: azidentity.NewWorkloadIdentityCredential,
armClientFunc: armresources.NewClient,
armMonitorDefinitionsClientFunc: armmonitor.NewMetricDefinitionsClient,
armMonitorMetricsClientFunc: armmonitor.NewMetricsClient,
Expand All @@ -103,6 +104,7 @@ type azureScraper struct {
resourcesUpdated time.Time
mb *metadata.MetricsBuilder
azIDCredentialsFunc func(string, string, string, *azidentity.ClientSecretCredentialOptions) (*azidentity.ClientSecretCredential, error)
azIDWorkloadFunc func(options *azidentity.WorkloadIdentityCredentialOptions) (*azidentity.WorkloadIdentityCredential, error)
armClientFunc func(string, azcore.TokenCredential, *arm.ClientOptions) (*armresources.Client, error)
armMonitorDefinitionsClientFunc func(azcore.TokenCredential, *arm.ClientOptions) (*armmonitor.MetricDefinitionsClient, error)
armMonitorMetricsClientFunc func(azcore.TokenCredential, *arm.ClientOptions) (*armmonitor.MetricsClient, error)
Expand Down Expand Up @@ -139,8 +141,7 @@ func (s *azureScraper) GetMetricsValuesClient() MetricsValuesClient {
}

func (s *azureScraper) start(_ context.Context, _ component.Host) (err error) {
s.cred, err = s.azIDCredentialsFunc(s.cfg.TenantID, s.cfg.ClientID, s.cfg.ClientSecret, nil)
if err != nil {
if err = s.loadCredentials(); err != nil {
return err
}

Expand All @@ -153,6 +154,22 @@ func (s *azureScraper) start(_ context.Context, _ component.Host) (err error) {
return
}

func (s *azureScraper) loadCredentials() (err error) {
switch s.cfg.Authentication {
case servicePrincipal:
if s.cred, err = s.azIDCredentialsFunc(s.cfg.TenantID, s.cfg.ClientID, s.cfg.ClientSecret, nil); err != nil {
return err
}
case workloadIdentity:
if s.cred, err = s.azIDWorkloadFunc(nil); err != nil {
return err
}
default:
return fmt.Errorf("unknown authentication %v", s.cfg.Authentication)
}
return nil
}

func (s *azureScraper) scrape(ctx context.Context) (pmetric.Metrics, error) {

s.getResources(ctx)
Expand Down
Loading

0 comments on commit 7831cd1

Please sign in to comment.