mirror of
https://github.com/go-acme/lego
synced 2026-03-14 14:35:48 +01:00
azuredns: pipeline credential support (#2621)
This commit is contained in:
parent
0012e20e52
commit
8a11af149f
4 changed files with 172 additions and 100 deletions
4
docs/content/dns/zz_gen_azuredns.md
generated
4
docs/content/dns/zz_gen_azuredns.md
generated
|
|
@ -229,6 +229,10 @@ This authentication method can be specifically used by setting the `AZURE_AUTH_M
|
|||
Open ID Connect is a mechanism that establish a trust relationship between a running environment and the Azure AD identity provider.
|
||||
It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `oidc`.
|
||||
|
||||
### Azure DevOps Pipelines
|
||||
|
||||
It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `pipeline`.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,19 +3,13 @@
|
|||
package azuredns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/platform/config/env"
|
||||
)
|
||||
|
||||
|
|
@ -33,10 +27,21 @@ const (
|
|||
EnvClientID = envNamespace + "CLIENT_ID"
|
||||
EnvClientSecret = envNamespace + "CLIENT_SECRET"
|
||||
|
||||
EnvOIDCToken = envNamespace + "OIDC_TOKEN"
|
||||
EnvOIDCTokenFilePath = envNamespace + "OIDC_TOKEN_FILE_PATH"
|
||||
EnvOIDCRequestURL = envNamespace + "OIDC_REQUEST_URL"
|
||||
EnvOIDCRequestToken = envNamespace + "OIDC_REQUEST_TOKEN"
|
||||
EnvOIDCToken = envNamespace + "OIDC_TOKEN"
|
||||
EnvOIDCTokenFilePath = envNamespace + "OIDC_TOKEN_FILE_PATH"
|
||||
EnvOIDCRequestURL = envNamespace + "OIDC_REQUEST_URL"
|
||||
EnvGitHubOIDCRequestURL = "ACTIONS_ID_TOKEN_REQUEST_URL"
|
||||
altEnvArmOIDCRequestURL = "ARM_OIDC_REQUEST_URL"
|
||||
EnvOIDCRequestToken = envNamespace + "OIDC_REQUEST_TOKEN"
|
||||
EnvGitHubOIDCRequestToken = "ACTIONS_ID_TOKEN_REQUEST_TOKEN"
|
||||
altEnvArmOIDCRequestToken = "ARM_OIDC_REQUEST_TOKEN"
|
||||
|
||||
EnvServiceConnectionID = envNamespace + "SERVICE_CONNECTION_ID"
|
||||
altEnvServiceConnectionID = "SERVICE_CONNECTION_ID"
|
||||
altEnvArmAdoPipelineServiceConnectionID = "ARM_ADO_PIPELINE_SERVICE_CONNECTION_ID"
|
||||
altEnvArmOIDCAzureServiceConnectionID = "ARM_OIDC_AZURE_SERVICE_CONNECTION_ID"
|
||||
EnvSystemAccessToken = envNamespace + "SYSTEM_ACCESS_TOKEN"
|
||||
altEnvSystemAccessToken = "SYSTEM_ACCESSTOKEN"
|
||||
|
||||
EnvAuthMethod = envNamespace + "AUTH_METHOD"
|
||||
EnvAuthMSITimeout = envNamespace + "AUTH_MSI_TIMEOUT"
|
||||
|
|
@ -46,9 +51,6 @@ const (
|
|||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
|
||||
EnvGitHubOIDCRequestURL = "ACTIONS_ID_TOKEN_REQUEST_URL"
|
||||
EnvGitHubOIDCRequestToken = "ACTIONS_ID_TOKEN_REQUEST_TOKEN"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
||||
|
|
@ -73,6 +75,9 @@ type Config struct {
|
|||
OIDCRequestURL string
|
||||
OIDCRequestToken string
|
||||
|
||||
ServiceConnectionID string
|
||||
SystemAccessToken string
|
||||
|
||||
AuthMethod string
|
||||
AuthMSITimeout time.Duration
|
||||
|
||||
|
|
@ -134,13 +139,22 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
config.ServiceDiscoveryFilter = env.GetOrFile(EnvServiceDiscoveryFilter)
|
||||
|
||||
oidcValues, _ := env.GetWithFallback(
|
||||
[]string{EnvOIDCRequestURL, EnvGitHubOIDCRequestURL},
|
||||
[]string{EnvOIDCRequestToken, EnvGitHubOIDCRequestToken},
|
||||
[]string{EnvOIDCRequestURL, EnvGitHubOIDCRequestURL, altEnvArmOIDCRequestURL},
|
||||
[]string{EnvOIDCRequestToken, EnvGitHubOIDCRequestToken, altEnvArmOIDCRequestToken},
|
||||
)
|
||||
|
||||
config.OIDCRequestURL = oidcValues[EnvOIDCRequestURL]
|
||||
config.OIDCRequestToken = oidcValues[EnvOIDCRequestToken]
|
||||
|
||||
// https://registry.terraform.io/providers/hashicorp/Azurerm/latest/docs/guides/service_principal_oidc
|
||||
pipelineValues, _ := env.GetWithFallback(
|
||||
[]string{EnvServiceConnectionID, altEnvServiceConnectionID, altEnvArmAdoPipelineServiceConnectionID, altEnvArmOIDCAzureServiceConnectionID},
|
||||
[]string{EnvSystemAccessToken, altEnvArmOIDCRequestToken, altEnvSystemAccessToken},
|
||||
)
|
||||
|
||||
config.ServiceConnectionID = pipelineValues[EnvServiceConnectionID]
|
||||
config.SystemAccessToken = pipelineValues[EnvSystemAccessToken]
|
||||
|
||||
config.AuthMethod = env.GetOrFile(EnvAuthMethod)
|
||||
config.AuthMSITimeout = env.GetOrDefaultSecond(EnvAuthMSITimeout, 2*time.Second)
|
||||
|
||||
|
|
@ -193,88 +207,3 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
return d.provider.CleanUp(domain, token, keyAuth)
|
||||
}
|
||||
|
||||
func getCredentials(config *Config) (azcore.TokenCredential, error) {
|
||||
clientOptions := azcore.ClientOptions{Cloud: config.Environment}
|
||||
|
||||
switch strings.ToLower(config.AuthMethod) {
|
||||
case "env":
|
||||
if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" {
|
||||
return azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret,
|
||||
&azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions})
|
||||
}
|
||||
|
||||
return azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{ClientOptions: clientOptions})
|
||||
|
||||
case "wli":
|
||||
return azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{ClientOptions: clientOptions})
|
||||
|
||||
case "msi":
|
||||
cred, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOptions})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &timeoutTokenCredential{cred: cred, timeout: config.AuthMSITimeout}, nil
|
||||
|
||||
case "cli":
|
||||
var credOptions *azidentity.AzureCLICredentialOptions
|
||||
if config.TenantID != "" {
|
||||
credOptions = &azidentity.AzureCLICredentialOptions{TenantID: config.TenantID}
|
||||
}
|
||||
return azidentity.NewAzureCLICredential(credOptions)
|
||||
|
||||
case "oidc":
|
||||
err := checkOIDCConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return azidentity.NewClientAssertionCredential(config.TenantID, config.ClientID, getOIDCAssertion(config), &azidentity.ClientAssertionCredentialOptions{ClientOptions: clientOptions})
|
||||
|
||||
default:
|
||||
return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: clientOptions})
|
||||
}
|
||||
}
|
||||
|
||||
// timeoutTokenCredential wraps a TokenCredential to add a timeout.
|
||||
type timeoutTokenCredential struct {
|
||||
cred azcore.TokenCredential
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// GetToken implements the azcore.TokenCredential interface.
|
||||
func (w *timeoutTokenCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
|
||||
if w.timeout <= 0 {
|
||||
return w.cred.GetToken(ctx, opts)
|
||||
}
|
||||
|
||||
ctxTimeout, cancel := context.WithTimeout(ctx, w.timeout)
|
||||
defer cancel()
|
||||
|
||||
tk, err := w.cred.GetToken(ctxTimeout, opts)
|
||||
if ce := ctxTimeout.Err(); errors.Is(ce, context.DeadlineExceeded) {
|
||||
return tk, azidentity.NewCredentialUnavailableError("managed identity timed out")
|
||||
}
|
||||
|
||||
w.timeout = 0
|
||||
|
||||
return tk, err
|
||||
}
|
||||
|
||||
func getZoneName(config *Config, fqdn string) (string, error) {
|
||||
if config.ZoneName != "" {
|
||||
return config.ZoneName, nil
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err)
|
||||
}
|
||||
|
||||
if authZone == "" {
|
||||
return "", errors.New("empty zone name")
|
||||
}
|
||||
|
||||
return authZone, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,10 @@ This authentication method can be specifically used by setting the `AZURE_AUTH_M
|
|||
Open ID Connect is a mechanism that establish a trust relationship between a running environment and the Azure AD identity provider.
|
||||
It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `oidc`.
|
||||
|
||||
### Azure DevOps Pipelines
|
||||
|
||||
It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `pipeline`.
|
||||
|
||||
'''
|
||||
|
||||
[Configuration]
|
||||
|
|
|
|||
135
providers/dns/azuredns/credentials.go
Normal file
135
providers/dns/azuredns/credentials.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package azuredns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
)
|
||||
|
||||
const (
|
||||
authMethodEnv = "env"
|
||||
authMethodWLI = "wli"
|
||||
authMethodMSI = "msi"
|
||||
authMethodCLI = "cli"
|
||||
authMethodOIDC = "oidc"
|
||||
authMethodPipeline = "pipeline"
|
||||
)
|
||||
|
||||
//nolint:gocyclo // The complexity is related to the number of possible configurations.
|
||||
func getCredentials(config *Config) (azcore.TokenCredential, error) {
|
||||
clientOptions := azcore.ClientOptions{Cloud: config.Environment}
|
||||
|
||||
switch strings.ToLower(config.AuthMethod) {
|
||||
case authMethodEnv:
|
||||
if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" {
|
||||
return azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret,
|
||||
&azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions})
|
||||
}
|
||||
|
||||
return azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{ClientOptions: clientOptions})
|
||||
|
||||
case authMethodWLI:
|
||||
return azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{ClientOptions: clientOptions})
|
||||
|
||||
case authMethodMSI:
|
||||
cred, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOptions})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &timeoutTokenCredential{cred: cred, timeout: config.AuthMSITimeout}, nil
|
||||
|
||||
case authMethodCLI:
|
||||
var credOptions *azidentity.AzureCLICredentialOptions
|
||||
if config.TenantID != "" {
|
||||
credOptions = &azidentity.AzureCLICredentialOptions{TenantID: config.TenantID}
|
||||
}
|
||||
return azidentity.NewAzureCLICredential(credOptions)
|
||||
|
||||
case authMethodOIDC:
|
||||
err := checkOIDCConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return azidentity.NewClientAssertionCredential(config.TenantID, config.ClientID, getOIDCAssertion(config), &azidentity.ClientAssertionCredentialOptions{ClientOptions: clientOptions})
|
||||
|
||||
case authMethodPipeline:
|
||||
err := checkPipelineConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Uses the env var `SYSTEM_OIDCREQUESTURI`,
|
||||
// but the constant is not exported,
|
||||
// and there is no way to set it programmatically.
|
||||
// https://github.com/Azure/azure-sdk-for-go/blob/aae2fb75ffccafc669db72bebc3c1a66332f48d7/sdk/azidentity/azure_pipelines_credential.go#L22
|
||||
// https://github.com/Azure/azure-sdk-for-go/blob/aae2fb75ffccafc669db72bebc3c1a66332f48d7/sdk/azidentity/azure_pipelines_credential.go#L79
|
||||
|
||||
return azidentity.NewAzurePipelinesCredential(config.TenantID, config.ClientID, config.ServiceConnectionID, config.SystemAccessToken, &azidentity.AzurePipelinesCredentialOptions{ClientOptions: clientOptions})
|
||||
|
||||
default:
|
||||
return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: clientOptions})
|
||||
}
|
||||
}
|
||||
|
||||
// timeoutTokenCredential wraps a TokenCredential to add a timeout.
|
||||
type timeoutTokenCredential struct {
|
||||
cred azcore.TokenCredential
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// GetToken implements the azcore.TokenCredential interface.
|
||||
func (w *timeoutTokenCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
|
||||
if w.timeout <= 0 {
|
||||
return w.cred.GetToken(ctx, opts)
|
||||
}
|
||||
|
||||
ctxTimeout, cancel := context.WithTimeout(ctx, w.timeout)
|
||||
defer cancel()
|
||||
|
||||
tk, err := w.cred.GetToken(ctxTimeout, opts)
|
||||
if ce := ctxTimeout.Err(); errors.Is(ce, context.DeadlineExceeded) {
|
||||
return tk, azidentity.NewCredentialUnavailableError("managed identity timed out")
|
||||
}
|
||||
|
||||
w.timeout = 0
|
||||
|
||||
return tk, err
|
||||
}
|
||||
|
||||
func getZoneName(config *Config, fqdn string) (string, error) {
|
||||
if config.ZoneName != "" {
|
||||
return config.ZoneName, nil
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err)
|
||||
}
|
||||
|
||||
if authZone == "" {
|
||||
return "", errors.New("empty zone name")
|
||||
}
|
||||
|
||||
return authZone, nil
|
||||
}
|
||||
|
||||
func checkPipelineConfig(config *Config) error {
|
||||
if config.ServiceConnectionID == "" {
|
||||
return errors.New("azuredns: ServiceConnectionID is missing")
|
||||
}
|
||||
|
||||
if config.SystemAccessToken == "" {
|
||||
return errors.New("azuredns: SystemAccessToken is missing")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue