Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AzureAD oauth for application registrations #2152

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Main (unreleased)

### Features

- Add `azuread.oauth` to `prometheus.remote_write` to support Azure AD authentication applicaion. (@callumau)

- Add `add_cloudwatch_timestamp` to `prometheus.exporter.cloudwatch` metrics. (@captncraig)

- Add support to `prometheus.operator.servicemonitors` to allow `endpointslice` role. (@yoyosir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ endpoint > oauth2 | [oauth2][] | Configure OAuth2 for authenticating to the endp
endpoint > oauth2 > tls_config | [tls_config][] | Configure TLS settings for connecting to the endpoint. | no
endpoint > sigv4 | [sigv4][] | Configure AWS Signature Verification 4 for authenticating to the endpoint. | no
endpoint > azuread | [azuread][] | Configure AzureAD for authenticating to the endpoint. | no
endpoint > azuread > managed_identity | [managed_identity][] | Configure Azure user-assigned managed identity. | yes
endpoint > azuread > managed_identity | [managed_identity][] | Configure Azure user-assigned managed identity. | no
endpoint > azuread > oauth | [oauth][] | Configure Azure application authenication. | no
endpoint > tls_config | [tls_config][] | Configure TLS settings for connecting to the endpoint. | no
endpoint > queue_config | [queue_config][] | Configuration for how metrics are batched before sending. | no
endpoint > metadata_config | [metadata_config][] | Configuration for how metric metadata is sent. | no
Expand All @@ -72,6 +73,7 @@ basic_auth` refers to a `basic_auth` block defined inside an
[sigv4]: #sigv4-block
[azuread]: #azuread-block
[managed_identity]: #managed_identity-block
[oauth]: #oauth-block
[tls_config]: #tls_config-block
[queue_config]: #queue_config-block
[metadata_config]: #metadata_config-block
Expand Down Expand Up @@ -151,6 +153,10 @@ metrics fails.

{{< docs/shared lookup="reference/components/managed_identity-block.md" source="alloy" version="<ALLOY_VERSION>" >}}

### oauth block

{{< docs/shared lookup="reference/components/oauth-block.md" source="alloy" version="<ALLOY_VERSION>" >}}

### tls_config block

{{< docs/shared lookup="reference/components/tls-config-block.md" source="alloy" version="<ALLOY_VERSION>" >}}
Expand Down
25 changes: 25 additions & 0 deletions docs/sources/shared/reference/components/oauth-block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
canonical: https://grafana.com/docs/alloy/latest/shared/reference/components/oauth-block/
description: Shared content, oauth block
headless: true
---

Name | Type | Description | Default | Required
----------------|----------|---------------------------------------------------------------------------------|---------|---------
`client_id` | `string` | Client ID of the Microsoft authentication application used to authenticate. | | yes
`client_secret` | `string` | Client secret of the Microsoft authentication application used to authenticate. | | yes
`tenant_id` | `string` | Tenant ID of the Microsoft authentication application used to authenticate. | | yes

`client_id` should be a valid [UUID][] in one of the supported formats:
* `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
* `urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
* Microsoft encoding: `{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}`
* Raw hex encoding: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`

`tenant_id` should be a valid [UUID][] in one of the supported formats:
* `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
* `urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
* Microsoft encoding: `{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}`
* Raw hex encoding: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`

[UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier
58 changes: 51 additions & 7 deletions internal/component/prometheus/remotewrite/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,15 +274,41 @@ type ManagedIdentityConfig struct {
ClientID string `alloy:"client_id,attr"`
}

func (m ManagedIdentityConfig) toPrometheusType() azuread.ManagedIdentityConfig {
return azuread.ManagedIdentityConfig{
func (m ManagedIdentityConfig) toPrometheusType() *azuread.ManagedIdentityConfig {
if m.ClientID == "" {
return nil
}

return &azuread.ManagedIdentityConfig{
ClientID: m.ClientID,
}
}

// Azure AD oauth
type AzureOAuthConfig struct {
// AzureADOAuth is the OAuth configuration that is being used to authenticate.
ClientID string `alloy:"client_id,attr"`
ClientSecret string `alloy:"client_secret,attr"`
TenantID string `alloy:"tenant_id,attr"`
}

func (m AzureOAuthConfig) toPrometheusType() *azuread.OAuthConfig {
if m.ClientID == "" && m.ClientSecret == "" && m.TenantID == "" {
return nil
}

return &azuread.OAuthConfig{
ClientID: m.ClientID,
ClientSecret: m.ClientSecret,
TenantID: m.TenantID,
}
}

type AzureADConfig struct {
// ManagedIdentity is the managed identity that is being used to authenticate.
ManagedIdentity ManagedIdentityConfig `alloy:"managed_identity,block"`
ManagedIdentity ManagedIdentityConfig `alloy:"managed_identity,block,optional"`
// OAuth is the OAuth configuration that is being used to authenticate.
OAuth AzureOAuthConfig `alloy:"oauth,block,optional"`

// Cloud is the Azure cloud in which the service is running. Example: AzurePublic/AzureGovernment/AzureChina.
Cloud string `alloy:"cloud,attr,optional"`
Expand All @@ -293,9 +319,25 @@ func (a *AzureADConfig) Validate() error {
return fmt.Errorf("must provide a cloud in the Azure AD config")
}

_, err := uuid.Parse(a.ManagedIdentity.ClientID)
if err != nil {
return fmt.Errorf("the provided Azure Managed Identity client_id provided is invalid")
// Ensure both Managed Identity and OAuth are not provided
if a.ManagedIdentity != (ManagedIdentityConfig{}) && a.OAuth != (AzureOAuthConfig{}) {
return fmt.Errorf("at most oauth or managed identity must be configured for azuread")
}

// Validate Managed Identity if it is provided
if (a.ManagedIdentity != ManagedIdentityConfig{}) {
_, err := uuid.Parse(a.ManagedIdentity.ClientID)
if err != nil {
return fmt.Errorf("the provided Azure Managed Identity client_id provided is invalid")
}
}

// Validate OAuth if it is provided
callumau marked this conversation as resolved.
Show resolved Hide resolved
if (a.OAuth != AzureOAuthConfig{}) {
_, err := uuid.Parse(a.OAuth.ClientID)
if err != nil {
return fmt.Errorf("the provided Azure Application Identity client_id provided is invalid")
}
}

return nil
Expand All @@ -314,8 +356,10 @@ func (a *AzureADConfig) toPrometheusType() *azuread.AzureADConfig {
}

mangedIdentity := a.ManagedIdentity.toPrometheusType()
oauth := a.OAuth.toPrometheusType()
return &azuread.AzureADConfig{
ManagedIdentity: &mangedIdentity,
OAuth: oauth,
ManagedIdentity: mangedIdentity,
Cloud: a.Cloud,
}
}
Expand Down
77 changes: 77 additions & 0 deletions internal/component/prometheus/remotewrite/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,32 @@ func TestAlloyConfig(t *testing.T) {
}
}),
},
{
testName: "AzureAD_Oauth",
cfg: `
endpoint {
url = "http://0.0.0.0:11111/api/v1/write"

azuread {
cloud = "AzureChina"
oauth {
client_id = "00000000-0000-0000-0000-000000000000"
tenant_id = "00000000-0000-0000-0000-000000000001"
client_secret = "00000000-0000-0000-0000-000000000002"
}
}
}`,
expectedCfg: expectedCfg(func(c *config.Config) {
c.RemoteWriteConfigs[0].AzureADConfig = &azuread.AzureADConfig{
Cloud: "AzureChina",
OAuth: &azuread.OAuthConfig{
ClientID: "00000000-0000-0000-0000-000000000000",
ClientSecret: "00000000-0000-0000-0000-000000000002",
TenantID: "00000000-0000-0000-0000-000000000001",
},
}
}),
},
{
testName: "SigV4_Defaults",
cfg: `
Expand Down Expand Up @@ -223,6 +249,26 @@ func TestAlloyConfig(t *testing.T) {
}`,
errorMsg: "at most one of sigv4, azuread, basic_auth, oauth2, bearer_token & bearer_token_file must be configured",
},
{
testName: "TooManyAuthAzureAD",
cfg: `
endpoint {
url = "http://0.0.0.0:11111/api/v1/write"

sigv4 {}
azuread {
managed_identity {
client_id = "00000000-0000-0000-0000-000000000000"
}
oauth {
client_id = "00000000-0000-0000-0000-000000000000"
tenant_id = "00000000-0000-0000-0000-000000000001"
client_secret = "00000000-0000-0000-0000-000000000002"
}
}
}`,
errorMsg: "at most oauth or managed identity must be configured for azuread",
},
{
testName: "BadAzureClientId",
cfg: `
Expand All @@ -237,6 +283,37 @@ func TestAlloyConfig(t *testing.T) {
}`,
errorMsg: "the provided Azure Managed Identity client_id provided is invalid",
},
{
testName: "BadAzureOAuthClientId",
cfg: `
endpoint {
url = "http://0.0.0.0:11111/api/v1/write"

azuread {
oauth {
client_id = "bad_client_id"
tenant_id = "00000000-0000-0000-0000-000000000001"
client_secret = "00000000-0000-0000-0000-000000000002"
}
}
}`,
errorMsg: "the provided Azure Application Identity client_id provided is invalid",
},
{
testName: "MissingAzureOAuthTenantId",
cfg: `
endpoint {
url = "http://0.0.0.0:11111/api/v1/write"

azuread {
oauth {
client_id = "bad_client_id"
client_secret = "00000000-0000-0000-0000-000000000002"
}
}
}`,
errorMsg: "missing required attribute \"tenant_id\"",
},
{
// Make sure the squashed HTTPClientConfig Validate function is being utilized correctly
testName: "BadBearerConfig",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,26 @@ func toAzureAD(azureADConfig *azuread.AzureADConfig) *remotewrite.AzureADConfig
return nil
}

return &remotewrite.AzureADConfig{
Cloud: azureADConfig.Cloud,
ManagedIdentity: remotewrite.ManagedIdentityConfig{
var oauth remotewrite.AzureOAuthConfig
var managedIdentity remotewrite.ManagedIdentityConfig

if azureADConfig.OAuth != nil {
oauth = remotewrite.AzureOAuthConfig{
ClientID: azureADConfig.OAuth.ClientID,
ClientSecret: azureADConfig.OAuth.ClientSecret,
TenantID: azureADConfig.OAuth.TenantID,
}
}

if azureADConfig.ManagedIdentity != nil {
managedIdentity = remotewrite.ManagedIdentityConfig{
ClientID: azureADConfig.ManagedIdentity.ClientID,
},
}
}

return &remotewrite.AzureADConfig{
Cloud: azureADConfig.Cloud,
ManagedIdentity: managedIdentity,
OAuth: oauth,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,23 @@ prometheus.remote_write "metrics_test7_azuread_explicit" {
}
}
}

prometheus.remote_write "metrics_test8_azuread_appauth" {
endpoint {
name = "test8_azuread_appauth-1654c7"
url = "http://localhost:9012/api/prom/push"

queue_config { }

metadata_config { }

azuread {
oauth {
client_id = "00000000-0000-0000-0000-000000000000"
client_secret = "fake_client_secret"
tenant_id = "00000000-0000-0000-0000-000000000000"
}
cloud = "AzureGovernment"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,13 @@ metrics:
cloud: AzureGovernment
managed_identity:
client_id: 00000000-0000-0000-0000-000000000000
- name: "test8_azuread_appauth"
remote_write:
- url: http://localhost:9012/api/prom/push
azuread:
cloud: AzureGovernment
oauth:
client_id: 00000000-0000-0000-0000-000000000000
client_secret: fake_client_secret
tenant_id: 00000000-0000-0000-0000-000000000000