Merge pull request #18217 from bragi92/main
Some checks are pending
buf.build / lint and publish (push) Waiting to run
CI / Go tests (push) Waiting to run
CI / More Go tests (push) Waiting to run
CI / Go tests for 32-bit x86 (push) Waiting to run
CI / Go tests for Prometheus upgrades and downgrades (push) Waiting to run
CI / Go tests with previous Go version (push) Waiting to run
CI / UI tests (push) Waiting to run
CI / Go tests on Windows (push) Waiting to run
CI / Mixins tests (push) Waiting to run
CI / Compliance testing (push) Waiting to run
CI / Build Prometheus for common architectures (push) Waiting to run
CI / Build Prometheus for all architectures (push) Waiting to run
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions
CI / Check generated parser (push) Waiting to run
CI / golangci-lint (push) Waiting to run
CI / fuzzing (push) Waiting to run
CI / codeql (push) Waiting to run
CI / Publish main branch artifacts (push) Blocked by required conditions
CI / Publish release artefacts (push) Blocked by required conditions
CI / Publish UI on npm Registry (push) Blocked by required conditions
govulncheck / Run govulncheck (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run

remote_write : Add Certificate support for ingesting data into an Azure Monitor Workspace
This commit is contained in:
Bartlomiej Plotka 2026-06-08 09:55:53 +00:00 committed by GitHub
commit d0db9b693c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 200 additions and 2 deletions

View file

@ -3831,6 +3831,18 @@ azuread:
[ client_secret: <string> ]
[ tenant_id: <string> ] ]
# Azure Certificate-based authentication.
[ certificate:
client_id: <string>
tenant_id: <string>
certificate_path: <file_name>
# Optional path to private key file if separate from certificate
[ certificate_key_path: <file_name> ]
# Optional password for password-protected certificate files (PFX/PKCS12)
[ certificate_password: <secret> ]
# Whether to send the certificate chain in the x5c header
[ send_certificate_chain: <boolean> | default = false ] ]
# Azure SDK auth.
# See https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication
[ sdk:

View file

@ -17,6 +17,7 @@ import (
"context"
"errors"
"net/http"
"os"
"strings"
"sync"
"time"
@ -88,6 +89,30 @@ type SDKConfig struct {
TenantID string `yaml:"tenant_id,omitempty"`
}
// CertificateConfig is used to store azure certificate-based authentication config values.
type CertificateConfig struct {
// ClientID is the clientId of the azure active directory application that is being used to authenticate.
ClientID string `yaml:"client_id,omitempty"`
// TenantID is the tenantId of the azure active directory application that is being used to authenticate.
TenantID string `yaml:"tenant_id,omitempty"`
// CertificatePath is the path to the certificate file (PEM or PFX format).
CertificatePath string `yaml:"certificate_path,omitempty"`
// CertificateKeyPath is the path to the private key file (PEM format).
// This is optional and only needed if the certificate and key are in separate files.
CertificateKeyPath string `yaml:"certificate_key_path,omitempty"`
// CertificatePassword is the password for the certificate file (for PFX files).
// This is optional and only needed if the certificate file is password-protected.
CertificatePassword config_util.Secret `yaml:"certificate_password,omitempty"`
// SendCertificateChain controls whether to include x5c header in assertion to support
// subject name / issuer-based authentication.
SendCertificateChain bool `yaml:"send_certificate_chain,omitempty"`
}
// AzureADConfig is used to store the config values.
type AzureADConfig struct { //nolint:revive // exported.
// ManagedIdentity is the managed identity that is being used to authenticate.
@ -102,6 +127,9 @@ type AzureADConfig struct { //nolint:revive // exported.
// SDK is the SDK config that is being used to authenticate.
SDK *SDKConfig `yaml:"sdk,omitempty"`
// Certificate is the certificate config that is being used to authenticate.
Certificate *CertificateConfig `yaml:"certificate,omitempty"`
// Cloud is the Azure cloud in which the service is running. Example: AzurePublic/AzureGovernment/AzureChina.
Cloud string `yaml:"cloud,omitempty"`
@ -151,9 +179,12 @@ func (c *AzureADConfig) Validate() error {
if c.SDK != nil {
authenticators++
}
if c.Certificate != nil {
authenticators++
}
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")
return errors.New("must provide an Azure Managed Identity, Azure Workload Identity, Azure OAuth, Azure Certificate or Azure SDK in the Azure AD config")
}
if authenticators > 1 {
return errors.New("cannot provide multiple authentication methods in the Azure AD config")
@ -215,6 +246,25 @@ func (c *AzureADConfig) Validate() error {
}
}
if c.Certificate != nil {
if c.Certificate.ClientID == "" {
return errors.New("must provide an Azure Certificate client_id in the Azure AD config")
}
if c.Certificate.TenantID == "" {
return errors.New("must provide an Azure Certificate tenant_id in the Azure AD config")
}
if c.Certificate.CertificatePath == "" {
return errors.New("must provide an Azure Certificate certificate_path in the Azure AD config")
}
if _, err := uuid.Parse(c.Certificate.ClientID); err != nil {
return errors.New("the provided Azure Certificate client_id is invalid")
}
if _, err := regexp.MatchString("^[0-9a-zA-Z-.]+$", c.Certificate.TenantID); err != nil {
return errors.New("the provided Azure Certificate tenant_id is invalid")
}
}
if c.Scope != "" {
if matched, err := regexp.MatchString("^[\\w\\s:/.\\-]+$", c.Scope); err != nil || !matched {
return errors.New("the provided scope contains invalid characters")
@ -325,6 +375,21 @@ func newTokenCredential(cfg *AzureADConfig) (azcore.TokenCredential, error) {
}
}
if cfg.Certificate != nil {
certificateConfig := &CertificateConfig{
ClientID: cfg.Certificate.ClientID,
TenantID: cfg.Certificate.TenantID,
CertificatePath: cfg.Certificate.CertificatePath,
CertificateKeyPath: cfg.Certificate.CertificateKeyPath,
CertificatePassword: cfg.Certificate.CertificatePassword,
SendCertificateChain: cfg.Certificate.SendCertificateChain,
}
cred, err = newCertificateTokenCredential(clientOpts, certificateConfig)
if err != nil {
return nil, err
}
}
return cred, nil
}
@ -367,6 +432,46 @@ func newSDKTokenCredential(clientOpts *azcore.ClientOptions, sdkConfig *SDKConfi
return azidentity.NewDefaultAzureCredential(opts)
}
// newCertificateTokenCredential returns new certificate-based token credential.
func newCertificateTokenCredential(clientOpts *azcore.ClientOptions, certConfig *CertificateConfig) (azcore.TokenCredential, error) {
certData, err := os.ReadFile(certConfig.CertificatePath)
if err != nil {
return nil, errors.New("failed to read certificate file " + certConfig.CertificatePath + ": " + err.Error())
}
// If a separate key file is provided, append it to the cert data so ParseCertificates can find the private key.
if certConfig.CertificateKeyPath != "" {
keyData, err := os.ReadFile(certConfig.CertificateKeyPath)
if err != nil {
return nil, errors.New("failed to read private key file " + certConfig.CertificateKeyPath + ": " + err.Error())
}
certData = append(append(certData, '\n'), keyData...)
}
var password []byte
if certConfig.CertificatePassword != "" {
password = []byte(certConfig.CertificatePassword)
}
certs, key, err := azidentity.ParseCertificates(certData, password)
if err != nil {
return nil, errors.New("failed to parse certificate data: " + err.Error())
}
opts := &azidentity.ClientCertificateCredentialOptions{
ClientOptions: *clientOpts,
SendCertificateChain: certConfig.SendCertificateChain,
}
return azidentity.NewClientCertificateCredential(
certConfig.TenantID,
certConfig.ClientID,
certs,
key,
opts,
)
}
// 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.
func newTokenProvider(cfg *AzureADConfig, cred azcore.TokenCredential) (*tokenProvider, error) {

View file

@ -157,7 +157,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 Workload Identity, Azure OAuth or Azure SDK in the Azure AD config",
err: "must provide an Azure Managed Identity, Azure Workload Identity, Azure OAuth, Azure Certificate or Azure SDK in the Azure AD config",
},
// Invalid managedidentity client id.
{
@ -232,6 +232,43 @@ func TestAzureAdConfig(t *testing.T) {
{
filename: "testdata/azuread_good_oauth_customscope.yaml",
},
// Valid certificate config.
{
filename: "testdata/azuread_good_certificate.yaml",
},
// Valid certificate config with separate key file.
{
filename: "testdata/azuread_good_certificate_with_key.yaml",
},
// Valid certificate config with PFX.
{
filename: "testdata/azuread_good_certificate_pfx.yaml",
},
// Missing certificate client id.
{
filename: "testdata/azuread_bad_certificate_missingclientid.yaml",
err: "must provide an Azure Certificate client_id in the Azure AD config",
},
// Missing certificate tenant id.
{
filename: "testdata/azuread_bad_certificate_missingtenantid.yaml",
err: "must provide an Azure Certificate tenant_id in the Azure AD config",
},
// Missing certificate path.
{
filename: "testdata/azuread_bad_certificate_missingpath.yaml",
err: "must provide an Azure Certificate certificate_path in the Azure AD config",
},
// Invalid certificate client id.
{
filename: "testdata/azuread_bad_certificate_invalidclientid.yaml",
err: "the provided Azure Certificate client_id is invalid",
},
// Invalid config when both certificate and oauth is provided.
{
filename: "testdata/azuread_bad_certificate_oauth.yaml",
err: "cannot provide multiple authentication methods in the Azure AD config",
},
}
for _, c := range cases {
_, err := loadAzureAdConfig(c.filename)

View file

@ -0,0 +1,5 @@
cloud: AzurePublic
certificate:
client_id: invalid-client-id
tenant_id: 00000000-a12b-3cd4-e56f-000000000000
certificate_path: /path/to/certificate.pem

View file

@ -0,0 +1,4 @@
cloud: AzurePublic
certificate:
tenant_id: 00000000-a12b-3cd4-e56f-000000000000
certificate_path: /path/to/certificate.pem

View file

@ -0,0 +1,4 @@
cloud: AzurePublic
certificate:
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: 00000000-a12b-3cd4-e56f-000000000000

View file

@ -0,0 +1,4 @@
cloud: AzurePublic
certificate:
client_id: 00000000-0000-0000-0000-000000000000
certificate_path: /path/to/certificate.pem

View file

@ -0,0 +1,9 @@
cloud: AzurePublic
certificate:
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: 00000000-a12b-3cd4-e56f-000000000000
certificate_path: /path/to/certificate.pem
oauth:
client_id: 00000000-0000-0000-0000-000000000000
client_secret: Cl1ent$ecret!
tenant_id: 00000000-a12b-3cd4-e56f-000000000000

View file

@ -0,0 +1,5 @@
cloud: AzurePublic
certificate:
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: 00000000-a12b-3cd4-e56f-000000000000
certificate_path: /path/to/certificate.pem

View file

@ -0,0 +1,6 @@
cloud: AzurePublic
certificate:
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: 00000000-a12b-3cd4-e56f-000000000000
certificate_path: /path/to/certificate.pfx
certificate_password: P@ssw0rd!

View file

@ -0,0 +1,7 @@
cloud: AzurePublic
certificate:
client_id: 00000000-0000-0000-0000-000000000000
tenant_id: 00000000-a12b-3cd4-e56f-000000000000
certificate_path: /path/to/certificate.pem
certificate_key_path: /path/to/key.pem
send_certificate_chain: true