Skip to content

Commit bf8c7ab

Browse files
pchanvallonldez
andauthored
azuredns: provide the ability to select authentication methods (go-acme#2026)
Co-authored-by: Fernandez Ludovic <[email protected]>
1 parent c2fd449 commit bf8c7ab

File tree

4 files changed

+198
-54
lines changed

4 files changed

+198
-54
lines changed

cmd/zz_gen_cmd_dnshelp.go

+3
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ func displayDNSHelp(w io.Writer, name string) error {
307307
ew.writeln()
308308

309309
ew.writeln(`Credentials:`)
310+
ew.writeln(` - "AZURE_CLIENT_CERTIFICATE_PATH": Client certificate path`)
310311
ew.writeln(` - "AZURE_CLIENT_ID": Client ID`)
311312
ew.writeln(` - "AZURE_CLIENT_SECRET": Client secret`)
312313
ew.writeln(` - "AZURE_RESOURCE_GROUP": DNS zone resource group`)
@@ -315,6 +316,8 @@ func displayDNSHelp(w io.Writer, name string) error {
315316
ew.writeln()
316317

317318
ew.writeln(`Additional Configuration:`)
319+
ew.writeln(` - "AZURE_AUTH_METHOD": Specify which authentication method to use`)
320+
ew.writeln(` - "AZURE_AUTH_MSI_TIMEOUT": Managed Identity timeout duration`)
318321
ew.writeln(` - "AZURE_ENVIRONMENT": Azure environment, one of: public, usgovernment, and china`)
319322
ew.writeln(` - "AZURE_POLLING_INTERVAL": Time between DNS propagation check`)
320323
ew.writeln(` - "AZURE_PRIVATE_ZONE": Set to true to use Azure Private DNS Zones and not public`)

docs/content/dns/zz_gen_azuredns.md

+62-15
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ lego --domains example.com --email [email protected] --dns azuredns run
7070

7171
| Environment Variable Name | Description |
7272
|-----------------------|-------------|
73+
| `AZURE_CLIENT_CERTIFICATE_PATH` | Client certificate path |
7374
| `AZURE_CLIENT_ID` | Client ID |
7475
| `AZURE_CLIENT_SECRET` | Client secret |
7576
| `AZURE_RESOURCE_GROUP` | DNS zone resource group |
@@ -84,6 +85,8 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
8485

8586
| Environment Variable Name | Description |
8687
|--------------------------------|-------------|
88+
| `AZURE_AUTH_METHOD` | Specify which authentication method to use |
89+
| `AZURE_AUTH_MSI_TIMEOUT` | Managed Identity timeout duration |
8790
| `AZURE_ENVIRONMENT` | Azure environment, one of: public, usgovernment, and china |
8891
| `AZURE_POLLING_INTERVAL` | Time between DNS propagation check |
8992
| `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" >}}).
9699

97100
## Description
98101

99-
Azure Credentials are automatically detected in the following locations and prioritized in the following order:
102+
Several authentication methods can be used to authenticate against Azure DNS API.
103+
104+
### Default Azure Credentials (default option)
105+
106+
Default Azure Credentials automatically detects in the following locations and prioritized in the following order:
100107

101108
1. Environment variables for client secret: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`
102109
2. Environment variables for client certificate: `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_CERTIFICATE_PATH`
103110
3. Workload identity for resources hosted in Azure environment (see below)
104-
4. Shared credentials file (defaults to `~/.azure`), used by Azure CLI
111+
4. Shared credentials (defaults to `~/.azure` folder), used by Azure CLI
105112

106113
Link:
107114
- [Azure Authentication](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication)
108115

116+
### Environment variables
117+
118+
#### Client secret
119+
120+
The Azure Credentials can be configured using the following environment variables:
121+
* AZURE_CLIENT_ID = "Client ID"
122+
* AZURE_CLIENT_SECRET = "Client secret"
123+
* AZURE_TENANT_ID = "Tenant ID"
124+
125+
This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
126+
127+
#### Client certificate
128+
129+
The Azure Credentials can be configured using the following environment variables:
130+
* AZURE_CLIENT_ID = "Client ID"
131+
* AZURE_CLIENT_CERTIFICATE_PATH = "Client certificate path"
132+
* AZURE_TENANT_ID = "Tenant ID"
133+
134+
This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `env`.
135+
109136
### Workload identity
110137

111-
#### Azure Managed Identity
138+
Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials.
139+
140+
This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand.
141+
142+
Here is a summary of the steps to follow to use it :
143+
* 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`.
144+
* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`.
145+
* 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.
146+
147+
Link :
148+
- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html)
149+
150+
This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `wli`.
151+
152+
### Azure Managed Identity
153+
154+
#### Azure Managed Identity (with Azure workload)
112155

113156
The Azure Managed Identity service allows linking Azure AD identities to Azure resources, without needing to manually manage client IDs and secrets.
114157

@@ -138,6 +181,11 @@ az role assignment create \
138181
--scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourceGroups/${AZURE_RESOURCE_GROUP}/providers/Microsoft.Network/dnszones/${AZURE_DNS_ZONE}/TXT/${AZ_RECORD_SET}"
139182
```
140183

184+
A timeout wrapper is configured for this authentication method.
185+
The duraction can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`.
186+
The default timeout is 2 seconds.
187+
This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`.
188+
141189
#### Azure Managed Identity (with Azure Arc)
142190

143191
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
146194
While the upstream `azidentity` SDK will try to automatically identify and use the Azure Arc metadata service,
147195
if you get `azuredns: DefaultAzureCredential: failed to acquire a token.` error messages,
148196
you may need to set the environment variables:
149-
* `IMDS_ENDPOINT=http://localhost:40342`
150-
* `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token`
151-
152-
#### Workload identity for AKS
197+
* `IMDS_ENDPOINT=http://localhost:40342`
198+
* `IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token`
153199

154-
Workload identity allows workloads running Azure Kubernetes Services (AKS) clusters to authenticate as an Azure AD application identity using federated credentials.
200+
A timeout wrapper is configured for this authentication method.
201+
The duraction can be configured by setting the `AZURE_AUTH_MSI_TIMEOUT`.
202+
The default timeout is 2 seconds.
203+
This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `msi`.
155204

156-
This must be configured in kubernetes workload deployment in one hand and on the Azure AD application registration in the other hand.
205+
### Azure CLI
157206

158-
Here is a summary of the steps to follow to use it :
159-
* 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`.
160-
* on the `Deployment` resource you must reference the previous `ServiceAccount` and add the following label : `azure.workload.identity/use: "true"`.
161-
* 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.
207+
The Azure CLI is a command-line tool provided by Microsoft to interact with Azure resources.
208+
It provides an easy way to authenticate by simply running `az login` command.
209+
The generated token will be cached by default in the `~/.azure` folder.
162210

163-
Link :
164-
- [Azure AD Workload identity](https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html)
211+
This authentication method can be specificaly used by setting the `AZURE_AUTH_METHOD` environment variable to `cli`.
165212

166213

167214

providers/dns/azuredns/azuredns.go

+71-24
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
package azuredns
44

55
import (
6+
"context"
67
"errors"
78
"fmt"
9+
"strings"
810
"time"
911

1012
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
1113
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
14+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
1215
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
1316
"github.com/go-acme/lego/v4/challenge"
1417
"github.com/go-acme/lego/v4/platform/config/env"
@@ -28,6 +31,9 @@ const (
2831
EnvClientID = envNamespace + "CLIENT_ID"
2932
EnvClientSecret = envNamespace + "CLIENT_SECRET"
3033

34+
EnvAuthMethod = envNamespace + "AUTH_METHOD"
35+
EnvAuthMSITimeout = envNamespace + "AUTH_MSI_TIMEOUT"
36+
3137
EnvTTL = envNamespace + "TTL"
3238
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
3339
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
@@ -46,6 +52,9 @@ type Config struct {
4652
ClientSecret string
4753
TenantID string
4854

55+
AuthMethod string
56+
AuthMSITimeout time.Duration
57+
4958
PropagationTimeout time.Duration
5059
PollingInterval time.Duration
5160
TTL int
@@ -94,6 +103,9 @@ func NewDNSProvider() (*DNSProvider, error) {
94103
config.ClientSecret = env.GetOrFile(EnvClientSecret)
95104
config.TenantID = env.GetOrFile(EnvTenantID)
96105

106+
config.AuthMethod = env.GetOrFile(EnvAuthMethod)
107+
config.AuthMSITimeout = env.GetOrDefaultSecond(EnvAuthMSITimeout, 2*time.Second)
108+
97109
return NewDNSProviderConfig(config)
98110
}
99111

@@ -103,30 +115,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
103115
return nil, errors.New("azuredns: the configuration of the DNS provider is nil")
104116
}
105117

106-
var err error
107-
var credentials azcore.TokenCredential
108-
if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" {
109-
options := azidentity.ClientSecretCredentialOptions{
110-
ClientOptions: azcore.ClientOptions{
111-
Cloud: config.Environment,
112-
},
113-
}
114-
115-
credentials, err = azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret, &options)
116-
if err != nil {
117-
return nil, fmt.Errorf("azuredns: %w", err)
118-
}
119-
} else {
120-
options := azidentity.DefaultAzureCredentialOptions{
121-
ClientOptions: azcore.ClientOptions{
122-
Cloud: config.Environment,
123-
},
124-
}
125-
126-
credentials, err = azidentity.NewDefaultAzureCredential(&options)
127-
if err != nil {
128-
return nil, fmt.Errorf("azuredns: %w", err)
129-
}
118+
credentials, err := getCredentials(config)
119+
if err != nil {
120+
return nil, fmt.Errorf("azuredns: Unable to retrieve valid credentials: %w", err)
130121
}
131122

132123
if config.SubscriptionID == "" {
@@ -153,6 +144,37 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
153144
return &DNSProvider{provider: dnsProvider}, nil
154145
}
155146

147+
func getCredentials(config *Config) (azcore.TokenCredential, error) {
148+
clientOptions := azcore.ClientOptions{Cloud: config.Environment}
149+
150+
switch strings.ToLower(config.AuthMethod) {
151+
case "env":
152+
if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" {
153+
return azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret,
154+
&azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions})
155+
}
156+
157+
return azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{ClientOptions: clientOptions})
158+
159+
case "wli":
160+
return azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{ClientOptions: clientOptions})
161+
162+
case "msi":
163+
cred, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOptions})
164+
if err != nil {
165+
return nil, err
166+
}
167+
168+
return &timeoutTokenCredential{cred: cred, timeout: config.AuthMSITimeout}, nil
169+
170+
case "cli":
171+
return azidentity.NewAzureCLICredential(nil)
172+
173+
default:
174+
return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: clientOptions})
175+
}
176+
}
177+
156178
// Timeout returns the timeout and interval to use when checking for DNS propagation.
157179
// Adjusting here to cope with spikes in propagation times.
158180
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
@@ -169,6 +191,31 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
169191
return d.provider.CleanUp(domain, token, keyAuth)
170192
}
171193

194+
// timeoutTokenCredential wraps a TokenCredential to add a timeout.
195+
type timeoutTokenCredential struct {
196+
cred azcore.TokenCredential
197+
timeout time.Duration
198+
}
199+
200+
// GetToken implements the azcore.TokenCredential interface.
201+
func (w *timeoutTokenCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
202+
if w.timeout <= 0 {
203+
return w.cred.GetToken(ctx, opts)
204+
}
205+
206+
ctxTimeout, cancel := context.WithTimeout(ctx, w.timeout)
207+
defer cancel()
208+
209+
tk, err := w.cred.GetToken(ctxTimeout, opts)
210+
if ce := ctxTimeout.Err(); errors.Is(ce, context.DeadlineExceeded) {
211+
return tk, azidentity.NewCredentialUnavailableError("managed identity timed out")
212+
}
213+
214+
w.timeout = 0
215+
216+
return tk, err
217+
}
218+
172219
func deref[T string | int | int32 | int64](v *T) T {
173220
if v == nil {
174221
var zero T

0 commit comments

Comments
 (0)