Skip to content

Commit 63bc3f3

Browse files
committed
validate ACR via Authorization header instead of query param
Signed-off-by: ruturaj220 <ruturajvaskar220@gmail.com>
1 parent f1ef6ff commit 63bc3f3

5 files changed

Lines changed: 74 additions & 26 deletions

File tree

cmd/webhook.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Supported registries:
4444
- Quay
4545
- Harbor
4646
- Aliyun ACR
47+
- Azure Container Registry (ACR)
4748
- AWS ECR (via EventBridge CloudEvents)
4849
`,
4950
RunE: func(cmd *cobra.Command, args []string) error {

docs/configuration/webhook.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,26 @@ https://app1.example.com/webhook?type=aliyun-acr&registry_url=my-instance-regist
129129

130130
### Azure Container Registry (ACR) Specifics
131131

132-
Azure Container Registry has **no built-in HMAC signing** for webhooks. The webhook
133-
creation form in the Azure portal only exposes a Service URI and free-form custom
134-
headers, neither of which can be used for signing. ACR therefore uses the
135-
[parameter secret](#parameter-secrets) pattern: include the secret as a query
136-
parameter in the Service URI you configure in Azure.
132+
Azure Container Registry has **no built-in HMAC signing** for webhooks, but it lets
133+
you attach arbitrary [custom HTTP headers](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-webhook-reference#http-headers)
134+
to its notifications. ACR therefore uses the same mechanism as the registries with
135+
[preexisting secret support](#registries-with-preexisting-support-for-secrets):
136+
configure an `Authorization` header on the webhook, and the handler validates it
137+
against the configured secret (constant-time comparison).
138+
139+
Set the header when creating or updating the webhook with the Azure CLI
140+
([reference](https://learn.microsoft.com/en-us/cli/azure/acr/webhook?view=azure-cli-latest#az-acr-webhook-update-examples)):
141+
142+
```bash
143+
az acr webhook update -n <webhook> -r <registry> --headers "Authorization=<YOUR_SECRET>"
144+
```
145+
146+
The value you set for the `Authorization` header must match the secret configured in
147+
`argocd-image-updater-secret` (`webhook.acr-secret`) / the `--acr-webhook-secret`
148+
flag. The webhook URL itself only needs the registry type:
137149

138150
```text
139-
https://app1.example.com/webhook?type=acr&secret=<YOUR_SECRET>
151+
https://app1.example.com/webhook?type=acr
140152
```
141153

142154
The registry hostname, repository, tag and digest are taken directly from the
@@ -274,6 +286,7 @@ Supported Registries That Use This:
274286
275287
- GitHub Container Registry
276288
- Harbor
289+
- Azure Container Registry (ACR)
277290
278291
### Parameter Secrets
279292
@@ -296,7 +309,6 @@ Supported Registries That Use This:
296309
- Docker Hub
297310
- Quay
298311
- Aliyun ACR
299-
- Azure Container Registry (ACR)
300312
- AWS ECR (via CloudEvents/EventBridge)
301313

302314
Also be aware that if the container registry has a built-in secrets method you will

docs/install/cmd/webhook.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,19 @@ Supported Registries:
1515
- Quay
1616
- Harbor
1717
- Aliyun ACR (Alibaba Cloud Container Registry)
18+
- Azure Container Registry (ACR)
1819
- AWS EventBridge (CloudEvents)
1920

2021
### Flags
2122

23+
**--acr-webhook-secret *secret***
24+
25+
Secret for validating Azure ACR webhooks. ACR has no built-in signing, so the
26+
secret is sent as the `Authorization` header value, which you configure on the
27+
webhook with `az acr webhook update --headers "Authorization=<secret>"`.
28+
29+
Can also be set with the `ACR_WEBHOOK_SECRET` environment variable.
30+
2231
**--aliyun-acr-webhook-secret *secret***
2332

2433
Secret for validating Aliyun ACR webhooks.

pkg/webhook/acr.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"net/http"
9+
"strings"
910

1011
"github.com/argoproj-labs/argocd-image-updater/pkg/argocd"
1112
)
@@ -33,17 +34,28 @@ func (a *ACRWebhook) Validate(r *http.Request) error {
3334
return fmt.Errorf("invalid HTTP method: %s", r.Method)
3435
}
3536

36-
// Azure ACR has no built-in HMAC signing. If a secret is configured, validate
37-
// it from the query parameter (parameter secret pattern).
38-
// !! This query param method is NOT secure, use at own risk
37+
// ACR sends a Content-Type of application/json unless a custom Content-Type
38+
// header is configured for the webhook.
39+
// See: https://learn.microsoft.com/en-us/azure/container-registry/container-registry-webhook-reference#http-headers
40+
contentType := r.Header.Get("Content-Type")
41+
if !strings.Contains(contentType, "application/json") {
42+
return fmt.Errorf("invalid content type: %s", contentType)
43+
}
44+
45+
// ACR has no built-in HMAC signing, but it lets you attach arbitrary custom
46+
// headers to webhook notifications via the Azure CLI, e.g.:
47+
// az acr webhook update -n <webhook> -r <registry> --headers "Authorization=Basic <token>"
48+
// We therefore validate the configured secret against the Authorization
49+
// header value. The secret configured in argocd-image-updater must match the full Authorization header value.
50+
// See: https://learn.microsoft.com/en-us/cli/azure/acr/webhook?view=azure-cli-latest#az-acr-webhook-update-examples
3951
if a.secret != "" {
40-
secret := r.URL.Query().Get("secret")
41-
if secret == "" {
42-
return fmt.Errorf("missing webhook secret")
52+
authHeader := r.Header.Get("Authorization")
53+
if authHeader == "" {
54+
return fmt.Errorf("missing Authorization header when secret is configured")
4355
}
4456

45-
if subtle.ConstantTimeCompare([]byte(secret), []byte(a.secret)) != 1 {
46-
return fmt.Errorf("invalid webhook secret")
57+
if subtle.ConstantTimeCompare([]byte(authHeader), []byte(a.secret)) != 1 {
58+
return fmt.Errorf("incorrect webhook secret")
4759
}
4860
}
4961

pkg/webhook/acr_test.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,38 +31,51 @@ func TestACRWebhook_Validate(t *testing.T) {
3131
tests := []struct {
3232
name string
3333
method string
34-
secret string
34+
contentType string
35+
authHeader string
3536
noSecret bool
3637
expectError bool
3738
}{
3839
{
3940
name: "valid POST request with correct secret",
4041
method: "POST",
41-
secret: "test-secret",
42+
contentType: "application/json",
43+
authHeader: "test-secret",
4244
expectError: false,
4345
},
4446
{
4547
name: "valid POST request without secret",
4648
method: "POST",
49+
contentType: "application/json",
4750
noSecret: true,
4851
expectError: false,
4952
},
5053
{
5154
name: "invalid HTTP method",
5255
method: "GET",
53-
secret: "test-secret",
56+
contentType: "application/json",
57+
authHeader: "test-secret",
5458
expectError: true,
5559
},
5660
{
57-
name: "missing secret when secret is configured",
61+
name: "invalid content type",
5862
method: "POST",
59-
secret: "",
63+
contentType: "text/plain",
64+
authHeader: "test-secret",
6065
expectError: true,
6166
},
6267
{
63-
name: "invalid secret",
68+
name: "missing Authorization header when secret is configured",
6469
method: "POST",
65-
secret: "not-the-secret",
70+
contentType: "application/json",
71+
authHeader: "",
72+
expectError: true,
73+
},
74+
{
75+
name: "incorrect secret",
76+
method: "POST",
77+
contentType: "application/json",
78+
authHeader: "not-the-secret",
6679
expectError: true,
6780
},
6881
}
@@ -75,10 +88,11 @@ func TestACRWebhook_Validate(t *testing.T) {
7588
}
7689

7790
req := httptest.NewRequest(tt.method, "/webhook", nil)
78-
if tt.secret != "" {
79-
query := req.URL.Query()
80-
query.Set("secret", tt.secret)
81-
req.URL.RawQuery = query.Encode()
91+
if tt.contentType != "" {
92+
req.Header.Set("Content-Type", tt.contentType)
93+
}
94+
if tt.authHeader != "" {
95+
req.Header.Set("Authorization", tt.authHeader)
8296
}
8397

8498
err := testWebhook.Validate(req)

0 commit comments

Comments
 (0)