Skip to content

Commit 6ff1113

Browse files
committed
feat: Add support for DefaultAzureCredential authentication mechanism
This commit adds support for the DefaultAzureCredential authentication mechanism in Azure Blob Storage. Users can now use the `useDefaultAzureCredentials` option to enable Azure's default credential chain, which automatically discovers and uses available credentials in the following order Signed-off-by: Armando Ruocco <[email protected]>
1 parent 367db3c commit 6ff1113

File tree

6 files changed

+299
-10
lines changed

6 files changed

+299
-10
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ toolchain go1.25.4
77
require (
88
github.com/cert-manager/cert-manager v1.19.1
99
github.com/cloudnative-pg/api v1.27.0
10-
github.com/cloudnative-pg/barman-cloud v0.3.4-0.20251203100017-1d476f125c5b
10+
github.com/cloudnative-pg/barman-cloud v0.3.4-0.20251216161014-c61e31075c40
1111
github.com/cloudnative-pg/cloudnative-pg v1.27.1
1212
github.com/cloudnative-pg/cnpg-i v0.3.0
1313
github.com/cloudnative-pg/cnpg-i-machinery v0.4.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
1818
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
1919
github.com/cloudnative-pg/api v1.27.0 h1:uSUkF9X/0UZu1Xn5qI33qHVmzZrDKuuyoiRlsOmSTv4=
2020
github.com/cloudnative-pg/api v1.27.0/go.mod h1:IWyAmuirffHiw6iIGD1p18BmZNb13TK9Os/wkp8ltDg=
21-
github.com/cloudnative-pg/barman-cloud v0.3.4-0.20251203100017-1d476f125c5b h1:7qpnZpOkmjhs0Prasu8laSaiEQ7eC2qW1xA39mQ/aEc=
22-
github.com/cloudnative-pg/barman-cloud v0.3.4-0.20251203100017-1d476f125c5b/go.mod h1:F6JqmFpa3V0/8paxu372tvxH7F6NrfUbtul3zrsoy+k=
21+
github.com/cloudnative-pg/barman-cloud v0.3.4-0.20251216161014-c61e31075c40 h1:EtYldJY2ddaNIicYvJBP4jK8OSOBQupTkrLAAUMWhEA=
22+
github.com/cloudnative-pg/barman-cloud v0.3.4-0.20251216161014-c61e31075c40/go.mod h1:F6JqmFpa3V0/8paxu372tvxH7F6NrfUbtul3zrsoy+k=
2323
github.com/cloudnative-pg/cloudnative-pg v1.27.1 h1:w+bbtXyEPoaa7sZGXxbb8qJ+/bUGWQ3M48kbNUEpKlk=
2424
github.com/cloudnative-pg/cloudnative-pg v1.27.1/go.mod h1:XbwCAlCm5fr+/A+v+qvMp8DHzVtJr2m0Y/TpKALw+Bk=
2525
github.com/cloudnative-pg/cnpg-i v0.3.0 h1:5ayNOG5x68lU70IVbHDZQrv5p+bErCJ0mqRmOpW2jjE=

internal/cnpgi/operator/specs/secrets.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ func CollectSecretNamesFromCredentials(barmanCredentials *barmanapi.BarmanCreden
3737
)
3838
}
3939
if barmanCredentials.Azure != nil {
40-
references = append(
41-
references,
42-
barmanCredentials.Azure.ConnectionString,
43-
barmanCredentials.Azure.StorageAccount,
44-
barmanCredentials.Azure.StorageKey,
45-
barmanCredentials.Azure.StorageSasToken,
46-
)
40+
// When using default Azure credentials, no secrets are required
41+
if !barmanCredentials.Azure.UseDefaultAzureCredentials {
42+
references = append(
43+
references,
44+
barmanCredentials.Azure.ConnectionString,
45+
barmanCredentials.Azure.StorageAccount,
46+
barmanCredentials.Azure.StorageKey,
47+
barmanCredentials.Azure.StorageSasToken,
48+
)
49+
}
4750
}
4851
if barmanCredentials.Google != nil {
4952
references = append(
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
Copyright © contributors to CloudNativePG, established as
3+
CloudNativePG a Series of LF Projects, LLC.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
SPDX-License-Identifier: Apache-2.0
18+
*/
19+
20+
package specs
21+
22+
import (
23+
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
24+
machineryapi "github.com/cloudnative-pg/machinery/pkg/api"
25+
26+
. "github.com/onsi/ginkgo/v2"
27+
. "github.com/onsi/gomega"
28+
)
29+
30+
var _ = Describe("CollectSecretNamesFromCredentials", func() {
31+
Context("when collecting secrets from AWS credentials", func() {
32+
It("should return secret names from S3 credentials", func() {
33+
credentials := &barmanapi.BarmanCredentials{
34+
AWS: &barmanapi.S3Credentials{
35+
AccessKeyIDReference: &machineryapi.SecretKeySelector{
36+
LocalObjectReference: machineryapi.LocalObjectReference{
37+
Name: "aws-secret",
38+
},
39+
Key: "access-key-id",
40+
},
41+
SecretAccessKeyReference: &machineryapi.SecretKeySelector{
42+
LocalObjectReference: machineryapi.LocalObjectReference{
43+
Name: "aws-secret",
44+
},
45+
Key: "secret-access-key",
46+
},
47+
},
48+
}
49+
50+
secrets := CollectSecretNamesFromCredentials(credentials)
51+
Expect(secrets).To(ContainElement("aws-secret"))
52+
})
53+
54+
It("should handle nil AWS credentials", func() {
55+
credentials := &barmanapi.BarmanCredentials{}
56+
57+
secrets := CollectSecretNamesFromCredentials(credentials)
58+
Expect(secrets).To(BeEmpty())
59+
})
60+
})
61+
62+
Context("when collecting secrets from Azure credentials", func() {
63+
It("should return secret names when using explicit credentials", func() {
64+
credentials := &barmanapi.BarmanCredentials{
65+
Azure: &barmanapi.AzureCredentials{
66+
ConnectionString: &machineryapi.SecretKeySelector{
67+
LocalObjectReference: machineryapi.LocalObjectReference{
68+
Name: "azure-secret",
69+
},
70+
Key: "connection-string",
71+
},
72+
},
73+
}
74+
75+
secrets := CollectSecretNamesFromCredentials(credentials)
76+
Expect(secrets).To(ContainElement("azure-secret"))
77+
})
78+
79+
It("should return empty list when using UseDefaultAzureCredentials", func() {
80+
credentials := &barmanapi.BarmanCredentials{
81+
Azure: &barmanapi.AzureCredentials{
82+
UseDefaultAzureCredentials: true,
83+
ConnectionString: &machineryapi.SecretKeySelector{
84+
LocalObjectReference: machineryapi.LocalObjectReference{
85+
Name: "azure-secret",
86+
},
87+
Key: "connection-string",
88+
},
89+
},
90+
}
91+
92+
secrets := CollectSecretNamesFromCredentials(credentials)
93+
Expect(secrets).To(BeEmpty())
94+
})
95+
96+
It("should return empty list when using InheritFromAzureAD", func() {
97+
credentials := &barmanapi.BarmanCredentials{
98+
Azure: &barmanapi.AzureCredentials{
99+
InheritFromAzureAD: true,
100+
},
101+
}
102+
103+
secrets := CollectSecretNamesFromCredentials(credentials)
104+
Expect(secrets).To(BeEmpty())
105+
})
106+
107+
It("should return secret names for storage account and key", func() {
108+
credentials := &barmanapi.BarmanCredentials{
109+
Azure: &barmanapi.AzureCredentials{
110+
StorageAccount: &machineryapi.SecretKeySelector{
111+
LocalObjectReference: machineryapi.LocalObjectReference{
112+
Name: "azure-storage",
113+
},
114+
Key: "account-name",
115+
},
116+
StorageKey: &machineryapi.SecretKeySelector{
117+
LocalObjectReference: machineryapi.LocalObjectReference{
118+
Name: "azure-storage",
119+
},
120+
Key: "account-key",
121+
},
122+
},
123+
}
124+
125+
secrets := CollectSecretNamesFromCredentials(credentials)
126+
Expect(secrets).To(ContainElement("azure-storage"))
127+
})
128+
})
129+
130+
Context("when collecting secrets from Google credentials", func() {
131+
It("should return secret names from Google credentials", func() {
132+
credentials := &barmanapi.BarmanCredentials{
133+
Google: &barmanapi.GoogleCredentials{
134+
ApplicationCredentials: &machineryapi.SecretKeySelector{
135+
LocalObjectReference: machineryapi.LocalObjectReference{
136+
Name: "google-secret",
137+
},
138+
Key: "credentials.json",
139+
},
140+
},
141+
}
142+
143+
secrets := CollectSecretNamesFromCredentials(credentials)
144+
Expect(secrets).To(ContainElement("google-secret"))
145+
})
146+
})
147+
148+
Context("when collecting secrets from multiple cloud providers", func() {
149+
It("should return secret names from all providers", func() {
150+
credentials := &barmanapi.BarmanCredentials{
151+
AWS: &barmanapi.S3Credentials{
152+
AccessKeyIDReference: &machineryapi.SecretKeySelector{
153+
LocalObjectReference: machineryapi.LocalObjectReference{
154+
Name: "aws-secret",
155+
},
156+
Key: "access-key-id",
157+
},
158+
},
159+
Azure: &barmanapi.AzureCredentials{
160+
ConnectionString: &machineryapi.SecretKeySelector{
161+
LocalObjectReference: machineryapi.LocalObjectReference{
162+
Name: "azure-secret",
163+
},
164+
Key: "connection-string",
165+
},
166+
},
167+
Google: &barmanapi.GoogleCredentials{
168+
ApplicationCredentials: &machineryapi.SecretKeySelector{
169+
LocalObjectReference: machineryapi.LocalObjectReference{
170+
Name: "google-secret",
171+
},
172+
Key: "credentials.json",
173+
},
174+
},
175+
}
176+
177+
secrets := CollectSecretNamesFromCredentials(credentials)
178+
Expect(secrets).To(ContainElements("aws-secret", "azure-secret", "google-secret"))
179+
})
180+
181+
It("should skip Azure secrets when using UseDefaultAzureCredentials with other providers", func() {
182+
credentials := &barmanapi.BarmanCredentials{
183+
AWS: &barmanapi.S3Credentials{
184+
AccessKeyIDReference: &machineryapi.SecretKeySelector{
185+
LocalObjectReference: machineryapi.LocalObjectReference{
186+
Name: "aws-secret",
187+
},
188+
Key: "access-key-id",
189+
},
190+
},
191+
Azure: &barmanapi.AzureCredentials{
192+
UseDefaultAzureCredentials: true,
193+
ConnectionString: &machineryapi.SecretKeySelector{
194+
LocalObjectReference: machineryapi.LocalObjectReference{
195+
Name: "azure-secret",
196+
},
197+
Key: "connection-string",
198+
},
199+
},
200+
}
201+
202+
secrets := CollectSecretNamesFromCredentials(credentials)
203+
Expect(secrets).To(ContainElement("aws-secret"))
204+
Expect(secrets).NotTo(ContainElement("azure-secret"))
205+
})
206+
})
207+
208+
Context("when handling nil references", func() {
209+
It("should skip nil secret references", func() {
210+
credentials := &barmanapi.BarmanCredentials{
211+
AWS: &barmanapi.S3Credentials{
212+
AccessKeyIDReference: &machineryapi.SecretKeySelector{
213+
LocalObjectReference: machineryapi.LocalObjectReference{
214+
Name: "aws-secret",
215+
},
216+
Key: "access-key-id",
217+
},
218+
SecretAccessKeyReference: nil,
219+
},
220+
}
221+
222+
secrets := CollectSecretNamesFromCredentials(credentials)
223+
Expect(secrets).To(ContainElement("aws-secret"))
224+
Expect(len(secrets)).To(Equal(1))
225+
})
226+
})
227+
})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
Copyright © contributors to CloudNativePG, established as
3+
CloudNativePG a Series of LF Projects, LLC.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
17+
SPDX-License-Identifier: Apache-2.0
18+
*/
19+
20+
package specs
21+
22+
import (
23+
"testing"
24+
25+
. "github.com/onsi/ginkgo/v2"
26+
. "github.com/onsi/gomega"
27+
)
28+
29+
func TestSpecs(t *testing.T) {
30+
RegisterFailHandler(Fail)
31+
RunSpecs(t, "Specs Suite")
32+
}

web/docs/object_stores.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,33 @@ spec:
252252
[...]
253253
```
254254

255+
### Default Azure Credentials
256+
257+
The `useDefaultAzureCredentials` option enables the default Azure credentials
258+
flow, which uses [`DefaultAzureCredential`](https://learn.microsoft.com/en-us/python/api/azure-identity/azure.identity.defaultazurecredential)
259+
to automatically discover and use available credentials in the following order:
260+
261+
1. **Environment Variables**`AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, and `AZURE_TENANT_ID` for Service Principal authentication
262+
2. **Managed Identity** — Uses the managed identity assigned to the pod
263+
3. **Azure CLI** — Uses credentials from the Azure CLI if available
264+
4. **Azure PowerShell** — Uses credentials from Azure PowerShell if available
265+
266+
This is particularly useful when running on Azure Kubernetes Service (AKS) with
267+
[Workload Identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview):
268+
269+
```yaml
270+
apiVersion: barmancloud.cnpg.io/v1
271+
kind: ObjectStore
272+
metadata:
273+
name: azure-store
274+
spec:
275+
configuration:
276+
destinationPath: "<destination path here>"
277+
azureCredentials:
278+
useDefaultAzureCredentials: true
279+
[...]
280+
```
281+
255282
### Access Key, SAS Token, or Connection String
256283

257284
Store credentials in a Kubernetes secret:

0 commit comments

Comments
 (0)