Repository: cert-manager/cert-manager (Go)

Issue:
AzureDNS panic when using invalid client ID (workload identity)
<!--
Bugs should be filed for issues encountered whilst operating cert-manager.
You should first attempt to resolve your issues through the community support
channels, e.g. Slack, in order to rule out individual configuration errors.
Please provide as much detail as possible. 
-->

**Describe the bug**:
<!--
A clear and concise description of what the bug is. 
Tip: you can use 
```
<code here>
```
for code blocks of your kubectl output or YAML files.
-->

When incorrectly specifying the managed identity client ID, cert-manager panics (`v1.15.1`):

```
I0627 11:15:05.256950       1 azuredns.go:99] "No ClientID found: attempting to authenticate with ambient credentials (Azure Workload Identity or Azure Managed Service Identity, in that order)" logger="cert-manager"
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x1c1abea]

goroutine 474 [running]:
github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/azuredns.(*DNSProvider).updateTXTRecord(0xc0010692c0, {0x2c9cfb8, 0xc0011c3a70}, {0xc00103fd10, 0x23}, 0xc0011fb730)
        github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/issuer/acme/dns/azuredns/azuredns.go:213 +0x22a
github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/azuredns.(*DNSProvider).Present(0x2ca31e0?, {0x2c9cfb8?, 0xc0011c3a70?}, {0x28e7156?, 0x25?}, {0xc00103fd10?, 0x0?}, {0xc000b69770?, 0xb7cca41e7945aedc?})
        github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/issuer/acme/dns/azuredns/azuredns.go:139 +0x5d
github.com/cert-manager/cert-manager/pkg/issuer/acme/dns.(*Solver).Present(0xc0005a6f00, {0x2c9cfb8, 0xc0011c3a40}, {0x2cbf700, 0xc000b2f4a0}, 0xc000eb4780)
        github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/issuer/acme/dns/dns.go:106 +0x3fb
github.com/cert-manager/cert-manager/pkg/controller/acmechallenges.(*controller).Sync(0xc0003de2d0, {0x2c9cfb8, 0xc0011c3a10}, 0xc000eb43c0)
        github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/acmechallenges/sync.go:177 +0xf93
github.com/cert-manager/cert-manager/pkg/controller/acmechallenges.(*controller).ProcessItem(0xc0003de2d0, {0x2c9cff0, 0xc000b6c5a0}, {0xc00014aa00, 0x41})
        github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/acmechallenges/controller.go:216 +0x1b0
github.com/cert-manager/cert-manager/pkg/controller.(*controller).worker.func1(0xc0007a1880, {0x22d6b80, 0xc001016830}, 0xc000cabf78, {{0x2ca31e0?, 0xc00025c600?}, 0x0?}, {0x2c9cff0, 0xc000b6c5a0})
        github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:154 +0x2bf
github.com/cert-manager/cert-manager/pkg/controller.(*controller).worker(0xc0007a1880, {0x2c9cff0, 0xc000b6c5a0})
        github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:171 +0xba
github.com/cert-manager/cert-manager/pkg/controller.(*controller).Run.func1()
        github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:108 +0x58
created by github.com/cert-manager/cert-manager/pkg/controller.(*controller).Run in goroutine 142
        github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:106 +0x227
```

When using cert-manager `v1.13.0`, we can see the actual error:

```
E0627 11:27:03.005762       1 azuredns.go:189] "cert-manager: failed to refresh token" err="adal: Refresh request failed. Status Code = '400'. Response body: {\"error\":\"unauthorized_client\",\"error_description\":\"AADSTS700016: Application with identifier 'redacted' was not found in the directory 'redacted'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant. Trace ID: redacted Correlation ID: redacted Timestamp: 2024-06-27 11:27:02Z\",\"error_codes\":[700016],\"timestamp\":\"2024-06-27 11:27:02Z\",\"trace_id\":\"redacted\",\"correlation_id\":\"redacted\",\"error_uri\":\"https://login.microsoftonline.com/error?code=700016\"} Endpoint https://login.microsoftonline.com/redacted/oauth2/token?api-version=1.0"
```

**Expected behaviour**:
<!--A concise description of what you expected to happen.-->

HTTP 400 error handled correctly

**Steps to reproduce the bug**:
<!--Steps to reproduce the bug should be clear and easily reproducible to help people
gain an understanding of the problem.-->

Specify incorrect client ID when using AzureDNS issuer with workload identity

**Anything else we need to know?**:

**Environment details:**:
- Kubernetes version: v1.29.2
- Cloud-provider/provisioner: Azure AKS
- cert-manager version: v1.15.1
- Install method: helm

/kind bug



Discussion:
I am also running into this issue after updating to cert-manager v1.15.1

```txt
E0709 08:43:30.563350       1 controller.go:162] "re-queuing item due to error processing" err="error creating new order: Get \"https://acme-v02.api.letsencrypt.org/directory\": net/http: TLS handshake timeout" logger="cert-manager.controller" key="dev-environment/my-tls-cert-1-1920001611"
W0709 08:43:37.783795       1 warnings.go:70] metadata.finalizers: "finalizer.acme.cert-manager.io": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers
I0709 08:43:37.962557       1 azuredns.go:99] "No ClientID found: attempting to authenticate with ambient credentials (Azure Workload Identity or Azure Managed Service Identity, in that order)" logger="cert-manager"
E0709 08:43:39.102788       1 sync.go:190] "propagation check failed" err="DNS record for \"bff-app.my-app.my-domain.com\" not yet propagated" logger="cert-manager.controller" resource_name="my-tls-cert-1-1920001611-905916928" resource_namespace="dev-environment" resource_kind="Challenge" resource_version="v1" dnsName="bff-app.my-app.my-domain.com" type="DNS-01"
```

- Kubernetes version: v1.29.4
- Cloud-provider/provisioner: Azure AKS
- cert-manager version: v1.15.1
- Install method: helm
When client secret is expired I get a similar panic:

```
I0710 15:49:56.296010       1 reflector.go:359] Caches populated for *v1.CertificateRequest from k8s.io/client-go@v0.30.1/tools/cache/reflector.go:232
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x1c1abea]

goroutine 597 [running]:
github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/azuredns.(*DNSProvider).updateTXTRecord(0xc0080c8060, {0x2c9cfb8, 0xc00150ebd0}, {0xc002aa9090, 0x45}, 0xc0040e7730)
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/issuer/acme/dns/azuredns/azuredns.go:213 +0x22a
github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/azuredns.(*DNSProvider).Present(0x2ca31e0?, {0x2c9cfb8?, 0xc00150ebd0?}, {0x28e7156?, 0x25?}, {0xc002aa9090?, 0x0?}, {0xc00121cd50?, 0x1c5dd99ffabc1c2f?})
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/issuer/acme/dns/azuredns/azuredns.go:139 +0x5d
github.com/cert-manager/cert-manager/pkg/issuer/acme/dns.(*Solver).Present(0xc00060ee10, {0x2c9cfb8, 0xc00150eba0}, {0x2cbf700, 0xc0012ab760}, 0xc000650000)
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/issuer/acme/dns/dns.go:106 +0x3fb
github.com/cert-manager/cert-manager/pkg/controller/acmechallenges.(*controller).Sync(0xc0003a82d0, {0x2c9cfb8, 0xc00150eb70}, 0xc000ce0d20)
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/acmechallenges/sync.go:177 +0xf93
github.com/cert-manager/cert-manager/pkg/controller/acmechallenges.(*controller).ProcessItem(0xc0003a82d0, {0x2c9cff0, 0xc000a08370}, {0xc0012e2460, 0x4d})
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/acmechallenges/controller.go:216 +0x1b0
github.com/cert-manager/cert-manager/pkg/controller.(*controller).worker.func1(0xc000462700, {0x22d6b80, 0xc00128edc0}, 0xc00152ef78, {{0x2ca31e0?, 0xc000594100?}, 0x0?}, {0x2c9cff0, 0xc000a08370})
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:154 +0x2bf
github.com/cert-manager/cert-manager/pkg/controller.(*controller).worker(0xc000462700, {0x2c9cff0, 0xc000a08370})
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:171 +0xba
github.com/cert-manager/cert-manager/pkg/controller.(*controller).Run.func1()
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:108 +0x58
created by github.com/cert-manager/cert-manager/pkg/controller.(*controller).Run in goroutine 139
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:106 +0x227
```
I apologize for that. The library behaves differently for authentication errors which I didn't catch during testing. 

Posted a PR with the fix.
I also see this error:
```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x1c1abea]

goroutine 522 [running]:
github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/azuredns.(*DNSProvider).updateTXTRecord(0xc0010d67e0, {0x2c9cfb8, 0xc0010dd8c0}, {0xc0010d2a20, 0x27}, 0xc0011b9730)
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/issuer/acme/dns/azuredns/azuredns.go:213 +0x22a
github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/azuredns.(*DNSProvider).Present(0x2ca31e0?, {0x2c9cfb8?, 0xc0010dd8c0?}, {0x28e7156?, 0x25?}, {0xc0010d2a20?, 0x0?}, {0xc000deca80?, 0xa3732e0cd3dce2a6?})
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/issuer/acme/dns/azuredns/azuredns.go:139 +0x5d
github.com/cert-manager/cert-manager/pkg/issuer/acme/dns.(*Solver).Present(0xc000af4af0, {0x2c9cfb8, 0xc0010dd890}, {0x2cbf700, 0xc000d50840}, 0xc0008aa000)
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/issuer/acme/dns/dns.go:106 +0x3fb
github.com/cert-manager/cert-manager/pkg/controller/acmechallenges.(*controller).Sync(0xc00021c2d0, {0x2c9cfb8, 0xc0010dd860}, 0xc000e503c0)
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/acmechallenges/sync.go:177 +0xf93
github.com/cert-manager/cert-manager/pkg/controller/acmechallenges.(*controller).ProcessItem(0xc00021c2d0, {0x2c9cff0, 0xc000bfe500}, {0xc000ded950, 0x30})
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/acmechallenges/controller.go:216 +0x1b0
github.com/cert-manager/cert-manager/pkg/controller.(*controller).worker.func1(0xc0008da500, {0x22d6b80, 0xc000fcc640}, 0xc001076f78, {{0x2ca31e0?, 0xc000144340?}, 0x0?}, {0x2c9cff0, 0xc000bfe500})
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:154 +0x2bf
github.com/cert-manager/cert-manager/pkg/controller.(*controller).worker(0xc0008da500, {0x2c9cff0, 0xc000bfe500})
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:171 +0xba
github.com/cert-manager/cert-manager/pkg/controller.(*controller).Run.func1()
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:108 +0x58
created by github.com/cert-manager/cert-manager/pkg/controller.(*controller).Run in goroutine 208
	github.com/cert-manager/cert-manager@v0.0.0-00010101000000-000000000000/pkg/controller/controller.go:106 +0x227
```

what's wrong with my clusterIssuer?
My dns zone is in a different subscription than the workload identity:

```yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  labels:
    argocd.argoproj.io/instance: cert-manager
  name: letsencrypt-prod-dns
spec:
  acme:
    email: devops@company.com
    preferredChain: ""
    privateKeySecretRef:
      name: letsencrypt-prod
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - dns01:
        azureDNS:
          environment: AzurePublicCloud
          hostedZoneName: company-sc-dev.com
          managedIdentity:
            clientID: REDACTED
          resourceGroupName: dns-zone-dev
          subscriptionID: REDACTED
status:
  acme:
    lastPrivateKeyHash: REDACTED
    lastRegisteredEmail: devops@company.com
    uri: https://acme-v02.api.letsencrypt.org/acme/acct/REDACTED
  conditions:
  - lastTransitionTime: "2024-07-18T10:16:05Z"
    message: The ACME account was registered with the ACME server
    observedGeneration: 1
    reason: ACMEAccountRegistered
    status: "True"
    type: Ready
```
@0x4c6565 @eplightning @travisghansen @mosheavni @Joren-Thijs-KasparSolutions .
We've published an alpha release containing the fix for this issue. Please test it and feedback:
 * https://github.com/cert-manager/cert-manager/releases/tag/v1.15.2-alpha.1

```sh
helm install cert-manager cert-manager \
  --repo https://charts.jetstack.io \
  --namespace cert-manager \
  --create-namespace \
  --wait \
  --version 1.15.2-alpha.1 \
  --set crds.enabled=true
```
The fix for this bug was released in https://github.com/cert-manager/cert-manager/releases/tag/v1.15.2 



Patch:
diff --git a/pkg/issuer/acme/dns/azuredns/azuredns.go b/pkg/issuer/acme/dns/azuredns/azuredns.go
index c6148feff0d..7fedb0cbd46 100644
--- a/pkg/issuer/acme/dns/azuredns/azuredns.go
+++ b/pkg/issuer/acme/dns/azuredns/azuredns.go
@@ -210,7 +210,7 @@ func (c *DNSProvider) updateTXTRecord(ctx context.Context, fqdn string, updater
 	resp, err := c.recordClient.Get(ctx, c.resourceGroupName, zone, name, dns.RecordTypeTXT, nil)
 	if err != nil {
 		var respErr *azcore.ResponseError
-		if errors.As(err, &respErr); respErr.StatusCode == http.StatusNotFound {
+		if errors.As(err, &respErr); respErr != nil && respErr.StatusCode == http.StatusNotFound {
 			set = &dns.RecordSet{
 				Properties: &dns.RecordSetProperties{
 					TTL:        to.Ptr(int64(60)),


Test patch:
diff --git a/pkg/issuer/acme/dns/azuredns/azuredns_test.go b/pkg/issuer/acme/dns/azuredns/azuredns_test.go
index 5823bab9836..7dcf94ed7f5 100644
--- a/pkg/issuer/acme/dns/azuredns/azuredns_test.go
+++ b/pkg/issuer/acme/dns/azuredns/azuredns_test.go
@@ -128,6 +128,17 @@ func TestInvalidAzureDns(t *testing.T) {
 	assert.Error(t, err)
 }
 
+func TestAuthenticationError(t *testing.T) {
+	provider, err := NewDNSProviderCredentials("", "invalid-client-id", "invalid-client-secret", "subid", "tenid", "rg", "example.com", util.RecursiveNameservers, false, &v1.AzureManagedIdentity{})
+	assert.NoError(t, err)
+
+	err = provider.Present(context.TODO(), "example.com", "_acme-challenge.example.com.", "123d==")
+	assert.Error(t, err)
+
+	err = provider.CleanUp(context.TODO(), "example.com", "_acme-challenge.example.com.", "123d==")
+	assert.Error(t, err)
+}
+
 func populateFederatedToken(t *testing.T, filename string, content string) {
 	t.Helper()