diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 47e3496f..62704757 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -307,6 +307,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Credentials:`) + ew.writeln(` - "AZURE_CLIENT_CERTIFICATE_PATH": Client certificate path`) ew.writeln(` - "AZURE_CLIENT_ID": Client ID`) ew.writeln(` - "AZURE_CLIENT_SECRET": Client secret`) ew.writeln(` - "AZURE_RESOURCE_GROUP": DNS zone resource group`) @@ -315,6 +316,8 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "AZURE_AUTH_METHOD": Specify which authentication method to use`) + ew.writeln(` - "AZURE_AUTH_MSI_TIMEOUT": Managed Identity timeout duration`) ew.writeln(` - "AZURE_ENVIRONMENT": Azure environment, one of: public, usgovernment, and china`) ew.writeln(` - "AZURE_POLLING_INTERVAL": Time between DNS propagation check`) ew.writeln(` - "AZURE_PRIVATE_ZONE": Set to true to use Azure Private DNS Zones and not public`) diff --git a/docs/content/dns/zz_gen_azuredns.md b/docs/content/dns/zz_gen_azuredns.md index 5cb55f4a..000be4c8 100644 --- a/docs/content/dns/zz_gen_azuredns.md +++ b/docs/content/dns/zz_gen_azuredns.md @@ -70,6 +70,7 @@ lego --domains example.com --email your_example@email.com --dns azuredns run | Environment Variable Name | Description | |-----------------------|-------------| +| `AZURE_CLIENT_CERTIFICATE_PATH` | Client certificate path | | `AZURE_CLIENT_ID` | Client ID | | `AZURE_CLIENT_SECRET` | Client secret | | `AZURE_RESOURCE_GROUP` | DNS zone resource group | @@ -84,6 +85,8 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `AZURE_AUTH_METHOD` | Specify which authentication method to use | +| `AZURE_AUTH_MSI_TIMEOUT` | Managed Identity timeout duration | | `AZURE_ENVIRONMENT` | Azure environment, one of: public, usgovernment, and china | | `AZURE_POLLING_INTERVAL` | Time between DNS propagation check | | `AZURE_PRIVATE_ZONE` | Set to true to use Azure Private DNS Zones and not public | @@ -96,19 +99,59 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}). ## Description -Azure Credentials are automatically detected in the following locations and prioritized in the following order: +Several authentication methods can be used to authenticate against Azure DNS API. + +### Default Azure Credentials (default option) + +Default Azure Credentials automatically detects in the following locations and prioritized in the following order: 1. Environment variables for client secret: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET` 2. Environment variables for client certificate: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_CERTIFICATE_PATH` 3. Workload identity for resources hosted in Azure environment (see below) -4. Shared credentials file (defaults to `~/.azure`), used by Azure CLI +4. Shared credentials (defaults to `~/.azure` folder), used by Azure CLI Link: - [Azure Authentication](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication) +### Environment variables + +#### Client secret + +The Azure Credentials can be configured using the following environment variables: +* AZURE_CLIENT_ID = "Client ID" +* AZURE_CLIENT_SECRET = "Client secret" +* AZURE_TENANT_ID = "Tenant ID" + +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `env`. + +#### Client certificate + +The Azure Credentials can be configured using the following environment variables: +* AZURE_CLIENT_ID = "Client ID" +* AZURE_CLIENT_CERTIFICATE_PATH = "Client certificate path" +* AZURE_TENANT_ID = "Tenant ID" + +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `env`. + ### Workload identity -#### Azure Managed Identity +Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials. + +This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand. + +Here is a summary of the steps to follow to use it : +* create a `ServiceAccount` resource, add following annotations to reference the targeted Azure AD application registration : `azure.workload.identity/client-id` and `azure.workload.identity/tenant-id`. +* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`. +* create a fedreated credentials of type `Kubernetes accessing Azure resources`, add the cluster issuer URL and add the namespace and name of your kubernetes service account. + +Link : +- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html) + +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `wli`. + +### Azure Managed Identity + +#### Azure Managed Identity (with Azure workload) The Azure Managed Identity service allows linking Azure AD identities to Azure resources, without needing to manually manage client IDs and secrets. @@ -138,6 +181,11 @@ az role assignment create \ --scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP}/providers/Microsoft.Network/dnszones/${AZURE_DNS_ZONE}/TXT/${AZ_RECORD_SET}" ``` +A timeout wrapper is configured for this authentication method. +The duraction can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`. +The default timeout is 2 seconds. +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`. + #### Azure Managed Identity (with Azure Arc) The Azure Arc agent provides the ability to use a Managed Identity on resources hosted outside of Azure @@ -146,22 +194,21 @@ The Azure Arc agent provides the ability to use a Managed Identity on resources While the upstream `azidentity` SDK will try to automatically identify and use the Azure Arc metadata service, if you get `azuredns: DefaultAzureCredential: failed to acquire a token.` error messages, you may need to set the environment variables: - * `IMDS_ENDPOINT=http://localhost:40342` - * `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token` +* `IMDS_ENDPOINT=http://localhost:40342` +* `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token` -#### Workload identity for AKS +A timeout wrapper is configured for this authentication method. +The duraction can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`. +The default timeout is 2 seconds. +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`. -Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials. +### Azure CLI -This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand. +The Azure CLI is a command-line tool provided by Microsoft to interact with Azure resources. +It provides an easy way to authenticate by simply running `az login` command. +The generated token will be cached by default in the `~/.azure` folder. -Here is a summary of the steps to follow to use it : -* create a `ServiceAccount` resource, add following annotations to reference the targeted Azure AD application registration : `azure.workload.identity/client-id` and `azure.workload.identity/tenant-id`. -* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`. -* create a fedreated credentials of type `Kubernetes accessing Azure resources`, add the cluster issuer URL and add the namespace and name of your kubernetes service account. - -Link : -- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html) +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `cli`. diff --git a/providers/dns/azuredns/azuredns.go b/providers/dns/azuredns/azuredns.go index 2839cc74..af5b8288 100644 --- a/providers/dns/azuredns/azuredns.go +++ b/providers/dns/azuredns/azuredns.go @@ -3,12 +3,15 @@ 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/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/platform/config/env" @@ -28,6 +31,9 @@ const ( EnvClientID = envNamespace + "CLIENT_ID" EnvClientSecret = envNamespace + "CLIENT_SECRET" + EnvAuthMethod = envNamespace + "AUTH_METHOD" + EnvAuthMSITimeout = envNamespace + "AUTH_MSI_TIMEOUT" + EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" @@ -46,6 +52,9 @@ type Config struct { ClientSecret string TenantID string + AuthMethod string + AuthMSITimeout time.Duration + PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -94,6 +103,9 @@ func NewDNSProvider() (*DNSProvider, error) { config.ClientSecret = env.GetOrFile(EnvClientSecret) config.TenantID = env.GetOrFile(EnvTenantID) + config.AuthMethod = env.GetOrFile(EnvAuthMethod) + config.AuthMSITimeout = env.GetOrDefaultSecond(EnvAuthMSITimeout, 2*time.Second) + return NewDNSProviderConfig(config) } @@ -103,30 +115,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("azuredns: the configuration of the DNS provider is nil") } - var err error - var credentials azcore.TokenCredential - if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" { - options := azidentity.ClientSecretCredentialOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: config.Environment, - }, - } - - credentials, err = azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret, &options) - if err != nil { - return nil, fmt.Errorf("azuredns: %w", err) - } - } else { - options := azidentity.DefaultAzureCredentialOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: config.Environment, - }, - } - - credentials, err = azidentity.NewDefaultAzureCredential(&options) - if err != nil { - return nil, fmt.Errorf("azuredns: %w", err) - } + credentials, err := getCredentials(config) + if err != nil { + return nil, fmt.Errorf("azuredns: Unable to retrieve valid credentials: %w", err) } if config.SubscriptionID == "" { @@ -153,6 +144,37 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return &DNSProvider{provider: dnsProvider}, nil } +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": + return azidentity.NewAzureCLICredential(nil) + + default: + return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: clientOptions}) + } +} + // Timeout returns the timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { @@ -169,6 +191,31 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return d.provider.CleanUp(domain, token, keyAuth) } +// 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 deref[T string | int | int32 | int64](v *T) T { if v == nil { var zero T diff --git a/providers/dns/azuredns/azuredns.toml b/providers/dns/azuredns/azuredns.toml index e8485873..0aea2bd8 100644 --- a/providers/dns/azuredns/azuredns.toml +++ b/providers/dns/azuredns/azuredns.toml @@ -45,19 +45,59 @@ lego --domains example.com --email your_example@email.com --dns azuredns run Additional = ''' ## Description -Azure Credentials are automatically detected in the following locations and prioritized in the following order: +Several authentication methods can be used to authenticate against Azure DNS API. + +### Default Azure Credentials (default option) + +Default Azure Credentials automatically detects in the following locations and prioritized in the following order: 1. Environment variables for client secret: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET` 2. Environment variables for client certificate: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_CERTIFICATE_PATH` 3. Workload identity for resources hosted in Azure environment (see below) -4. Shared credentials file (defaults to `~/.azure`), used by Azure CLI +4. Shared credentials (defaults to `~/.azure` folder), used by Azure CLI Link: - [Azure Authentication](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication) +### Environment variables + +#### Client secret + +The Azure Credentials can be configured using the following environment variables: +* AZURE_CLIENT_ID = "Client ID" +* AZURE_CLIENT_SECRET = "Client secret" +* AZURE_TENANT_ID = "Tenant ID" + +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `env`. + +#### Client certificate + +The Azure Credentials can be configured using the following environment variables: +* AZURE_CLIENT_ID = "Client ID" +* AZURE_CLIENT_CERTIFICATE_PATH = "Client certificate path" +* AZURE_TENANT_ID = "Tenant ID" + +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `env`. + ### Workload identity -#### Azure Managed Identity +Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials. + +This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand. + +Here is a summary of the steps to follow to use it : +* create a `ServiceAccount` resource, add following annotations to reference the targeted Azure AD application registration : `azure.workload.identity/client-id` and `azure.workload.identity/tenant-id`. +* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`. +* create a fedreated credentials of type `Kubernetes accessing Azure resources`, add the cluster issuer URL and add the namespace and name of your kubernetes service account. + +Link : +- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html) + +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `wli`. + +### Azure Managed Identity + +#### Azure Managed Identity (with Azure workload) The Azure Managed Identity service allows linking Azure AD identities to Azure resources, without needing to manually manage client IDs and secrets. @@ -87,6 +127,11 @@ az role assignment create \ --scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP}/providers/Microsoft.Network/dnszones/${AZURE_DNS_ZONE}/TXT/${AZ_RECORD_SET}" ``` +A timeout wrapper is configured for this authentication method. +The duraction can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`. +The default timeout is 2 seconds. +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`. + #### Azure Managed Identity (with Azure Arc) The Azure Arc agent provides the ability to use a Managed Identity on resources hosted outside of Azure @@ -95,22 +140,21 @@ The Azure Arc agent provides the ability to use a Managed Identity on resources While the upstream `azidentity` SDK will try to automatically identify and use the Azure Arc metadata service, if you get `azuredns: DefaultAzureCredential: failed to acquire a token.` error messages, you may need to set the environment variables: - * `IMDS_ENDPOINT=http://localhost:40342` - * `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token` +* `IMDS_ENDPOINT=http://localhost:40342` +* `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token` -#### Workload identity for AKS +A timeout wrapper is configured for this authentication method. +The duraction can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`. +The default timeout is 2 seconds. +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`. -Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials. +### Azure CLI -This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand. +The Azure CLI is a command-line tool provided by Microsoft to interact with Azure resources. +It provides an easy way to authenticate by simply running `az login` command. +The generated token will be cached by default in the `~/.azure` folder. -Here is a summary of the steps to follow to use it : -* create a `ServiceAccount` resource, add following annotations to reference the targeted Azure AD application registration : `azure.workload.identity/client-id` and `azure.workload.identity/tenant-id`. -* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`. -* create a fedreated credentials of type `Kubernetes accessing Azure resources`, add the cluster issuer URL and add the namespace and name of your kubernetes service account. - -Link : -- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html) +This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `cli`. ''' @@ -119,12 +163,15 @@ Link : AZURE_CLIENT_ID = "Client ID" AZURE_CLIENT_SECRET = "Client secret" AZURE_TENANT_ID = "Tenant ID" + AZURE_CLIENT_CERTIFICATE_PATH = "Client certificate path" AZURE_SUBSCRIPTION_ID = "DNS zone subscription ID" AZURE_RESOURCE_GROUP = "DNS zone resource group" [Configuration.Additional] AZURE_ENVIRONMENT = "Azure environment, one of: public, usgovernment, and china" AZURE_PRIVATE_ZONE = "Set to true to use Azure Private DNS Zones and not public" AZURE_ZONE_NAME = "Zone name to use inside Azure DNS service to add the TXT record in" + AZURE_AUTH_METHOD = "Specify which authentication method to use" + AZURE_AUTH_MSI_TIMEOUT = "Managed Identity timeout duration" AZURE_TTL = "The TTL of the TXT record used for the DNS challenge" AZURE_POLLING_INTERVAL = "Time between DNS propagation check" AZURE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"