Compare commits

...

6 Commits

Author SHA1 Message Date
Armando Ruocco
765aa52b54
Merge 631ff20ed1 into afa39ba786 2025-12-30 15:37:31 +01:00
Marco Nenciarini
631ff20ed1
chore: fix spellcheck
Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
2025-12-29 18:17:56 +01:00
Marco Nenciarini
5e71e91a78
chore: implement Gabriele's suggestion
Signed-off-by: Marco Nenciarini <marco.nenciarini@enterprisedb.com>
2025-12-29 18:17:56 +01:00
Gabriele Fedi
45884774db
docs: inheritFromAzureAD for managed identities (review #681) (#698)
Make explicit in docs that the inheritFromAzureAD option enables the
usage of Azure Managed Identity authentication mechanism.

Signed-off-by: Gabriele Fedi <gabriele.fedi@enterprisedb.com>
2025-12-29 18:17:56 +01:00
Armando Ruocco
f68e348cd4
fix: manifests
Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
2025-12-29 18:17:56 +01:00
Armando Ruocco
efc7d5cd44
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 <armando.ruocco@enterprisedb.com>
2025-12-29 18:17:55 +01:00
8 changed files with 320 additions and 14 deletions

View File

@ -1,3 +1,4 @@
AKS
AccessDenied
AdditionalContainerArgs
Akamai
@ -5,6 +6,7 @@ Azurite
BarmanObjectStore
BarmanObjectStoreConfiguration
BarmanObjectStores
CLI
CNCF
CRD
CloudNativePG
@ -38,6 +40,7 @@ PITR
PoR
PostgreSQL
Postgres
PowerShell
README
RPO
RTO
@ -45,6 +48,7 @@ RecoveryWindow
ResourceRequirements
RetentionPolicy
SAS
SDK
SFO
SPDX
SPDX

View File

@ -108,6 +108,11 @@ spec:
- key
- name
type: object
useDefaultAzureCredentials:
description: |-
Use the default Azure authentication flow, which includes DefaultAzureCredential.
This allows authentication using environment variables and managed identities.
type: boolean
type: object
data:
description: |-

2
go.mod
View File

@ -7,7 +7,7 @@ toolchain go1.25.5
require (
github.com/cert-manager/cert-manager v1.19.2
github.com/cloudnative-pg/api v1.28.0
github.com/cloudnative-pg/barman-cloud v0.4.0
github.com/cloudnative-pg/barman-cloud v0.4.1-0.20251229132714-810c497a61b5
github.com/cloudnative-pg/cloudnative-pg v1.28.0
github.com/cloudnative-pg/cnpg-i v0.3.1
github.com/cloudnative-pg/cnpg-i-machinery v0.4.2

4
go.sum
View File

@ -18,8 +18,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudnative-pg/api v1.28.0 h1:xElzHliO0eKkVQafkfMhDJo0aIRCmB1ItEt+SGh6B58=
github.com/cloudnative-pg/api v1.28.0/go.mod h1:puXJBOsEaJd8JLgvCtxgl2TO/ZANap/z7bPepKRUgrk=
github.com/cloudnative-pg/barman-cloud v0.4.0 h1:V4ajM5yDWq2m+TxmnDtCBGmfMXAxbXr9k7lfR4jM+eE=
github.com/cloudnative-pg/barman-cloud v0.4.0/go.mod h1:AWdyNP2jvMO1c7eOOwT8kT+QGyK5O7lEBZX12LEZ1Ic=
github.com/cloudnative-pg/barman-cloud v0.4.1-0.20251229132714-810c497a61b5 h1:BZq6B52w9A2zOhpN7V40Cbn7gFqKQtz0K6BhRT5R+dA=
github.com/cloudnative-pg/barman-cloud v0.4.1-0.20251229132714-810c497a61b5/go.mod h1:AWdyNP2jvMO1c7eOOwT8kT+QGyK5O7lEBZX12LEZ1Ic=
github.com/cloudnative-pg/cloudnative-pg v1.28.0 h1:vkv0a0ewDSfJOPJrsyUr4uczsxheReAWf/k171V0Dm0=
github.com/cloudnative-pg/cloudnative-pg v1.28.0/go.mod h1:209fkRR6m0vXUVQ9Q498eAPQqN2UlXECbXXtpGsZz3I=
github.com/cloudnative-pg/cnpg-i v0.3.1 h1:fKj8NoToWI11HUL2UWYJBpkVzmaTvbs3kDMo7wQF8RU=

View File

@ -37,13 +37,17 @@ func CollectSecretNamesFromCredentials(barmanCredentials *barmanapi.BarmanCreden
)
}
if barmanCredentials.Azure != nil {
references = append(
references,
barmanCredentials.Azure.ConnectionString,
barmanCredentials.Azure.StorageAccount,
barmanCredentials.Azure.StorageKey,
barmanCredentials.Azure.StorageSasToken,
)
// When using default Azure credentials or managed identity, no secrets are required
if !barmanCredentials.Azure.UseDefaultAzureCredentials &&
!barmanCredentials.Azure.InheritFromAzureAD {
references = append(
references,
barmanCredentials.Azure.ConnectionString,
barmanCredentials.Azure.StorageAccount,
barmanCredentials.Azure.StorageKey,
barmanCredentials.Azure.StorageSasToken,
)
}
}
if barmanCredentials.Google != nil {
references = append(

View File

@ -0,0 +1,227 @@
/*
Copyright © contributors to CloudNativePG, established as
CloudNativePG a Series of LF Projects, LLC.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache-2.0
*/
package specs
import (
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
machineryapi "github.com/cloudnative-pg/machinery/pkg/api"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("CollectSecretNamesFromCredentials", func() {
Context("when collecting secrets from AWS credentials", func() {
It("should return secret names from S3 credentials", func() {
credentials := &barmanapi.BarmanCredentials{
AWS: &barmanapi.S3Credentials{
AccessKeyIDReference: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "aws-secret",
},
Key: "access-key-id",
},
SecretAccessKeyReference: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "aws-secret",
},
Key: "secret-access-key",
},
},
}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(ContainElement("aws-secret"))
})
It("should handle nil AWS credentials", func() {
credentials := &barmanapi.BarmanCredentials{}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(BeEmpty())
})
})
Context("when collecting secrets from Azure credentials", func() {
It("should return secret names when using explicit credentials", func() {
credentials := &barmanapi.BarmanCredentials{
Azure: &barmanapi.AzureCredentials{
ConnectionString: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "azure-secret",
},
Key: "connection-string",
},
},
}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(ContainElement("azure-secret"))
})
It("should return empty list when using UseDefaultAzureCredentials", func() {
credentials := &barmanapi.BarmanCredentials{
Azure: &barmanapi.AzureCredentials{
UseDefaultAzureCredentials: true,
ConnectionString: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "azure-secret",
},
Key: "connection-string",
},
},
}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(BeEmpty())
})
It("should return empty list when using InheritFromAzureAD", func() {
credentials := &barmanapi.BarmanCredentials{
Azure: &barmanapi.AzureCredentials{
InheritFromAzureAD: true,
},
}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(BeEmpty())
})
It("should return secret names for storage account and key", func() {
credentials := &barmanapi.BarmanCredentials{
Azure: &barmanapi.AzureCredentials{
StorageAccount: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "azure-storage",
},
Key: "account-name",
},
StorageKey: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "azure-storage",
},
Key: "account-key",
},
},
}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(ContainElement("azure-storage"))
})
})
Context("when collecting secrets from Google credentials", func() {
It("should return secret names from Google credentials", func() {
credentials := &barmanapi.BarmanCredentials{
Google: &barmanapi.GoogleCredentials{
ApplicationCredentials: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "google-secret",
},
Key: "credentials.json",
},
},
}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(ContainElement("google-secret"))
})
})
Context("when collecting secrets from multiple cloud providers", func() {
It("should return secret names from all providers", func() {
credentials := &barmanapi.BarmanCredentials{
AWS: &barmanapi.S3Credentials{
AccessKeyIDReference: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "aws-secret",
},
Key: "access-key-id",
},
},
Azure: &barmanapi.AzureCredentials{
ConnectionString: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "azure-secret",
},
Key: "connection-string",
},
},
Google: &barmanapi.GoogleCredentials{
ApplicationCredentials: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "google-secret",
},
Key: "credentials.json",
},
},
}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(ContainElements("aws-secret", "azure-secret", "google-secret"))
})
It("should skip Azure secrets when using UseDefaultAzureCredentials with other providers", func() {
credentials := &barmanapi.BarmanCredentials{
AWS: &barmanapi.S3Credentials{
AccessKeyIDReference: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "aws-secret",
},
Key: "access-key-id",
},
},
Azure: &barmanapi.AzureCredentials{
UseDefaultAzureCredentials: true,
ConnectionString: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "azure-secret",
},
Key: "connection-string",
},
},
}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(ContainElement("aws-secret"))
Expect(secrets).NotTo(ContainElement("azure-secret"))
})
})
Context("when handling nil references", func() {
It("should skip nil secret references", func() {
credentials := &barmanapi.BarmanCredentials{
AWS: &barmanapi.S3Credentials{
AccessKeyIDReference: &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{
Name: "aws-secret",
},
Key: "access-key-id",
},
SecretAccessKeyReference: nil,
},
}
secrets := CollectSecretNamesFromCredentials(credentials)
Expect(secrets).To(ContainElement("aws-secret"))
Expect(len(secrets)).To(Equal(1))
})
})
})

View File

@ -0,0 +1,32 @@
/*
Copyright © contributors to CloudNativePG, established as
CloudNativePG a Series of LF Projects, LLC.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache-2.0
*/
package specs
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestSpecs(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Specs Suite")
}

View File

@ -232,12 +232,16 @@ Barman Cloud supports the following authentication methods:
- [Connection String](https://learn.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string)
- Storage Account Name + [Access Key](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage)
- Storage Account Name + [SAS Token](https://learn.microsoft.com/en-us/azure/storage/blobs/sas-service-create)
- [Azure AD Workload Identity](https://azure.github.io/azure-workload-identity/docs/introduction.html)
- [Azure AD Managed Identities](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview)
- [Default Azure Credentials](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet)
### Azure AD Workload Identity
### Azure AD Managed Identity
This method avoids storing credentials in Kubernetes via the
`.spec.configuration.inheritFromAzureAD` option:
This method avoids storing credentials in Kubernetes by enabling the
usage of [Azure Managed Identities](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) authentication mechanism.
This can be enabled by setting the `inheritFromAzureAD` option to `true`.
Managed Identity can be configured for the AKS Cluster by following
the [Azure documentation](https://learn.microsoft.com/en-us/azure/aks/use-managed-identity?pivots=system-assigned).
```yaml
apiVersion: barmancloud.cnpg.io/v1
@ -252,6 +256,36 @@ spec:
[...]
```
### Default Azure Credentials
The `useDefaultAzureCredentials` option enables the default Azure credentials
flow, which uses [`DefaultAzureCredential`](https://learn.microsoft.com/en-us/python/api/azure-identity/azure.identity.defaultazurecredential)
to automatically discover and use available credentials in the following order:
1. **Environment Variables**`AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, and `AZURE_TENANT_ID` for Service Principal authentication
2. **Managed Identity** — Uses the managed identity assigned to the pod
3. **Azure CLI** — Uses credentials from the Azure CLI if available
4. **Azure PowerShell** — Uses credentials from Azure PowerShell if available
This approach is particularly useful for getting started with development and testing; it allows
the SDK to attempt multiple authentication mechanisms seamlessly across different environments.
However, this is not recommended for production. Please refer to the
[official Azure guidance](https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/credential-chains?tabs=dac#usage-guidance-for-defaultazurecredential)
for a comprehensive understanding of `DefaultAzureCredential`.
```yaml
apiVersion: barmancloud.cnpg.io/v1
kind: ObjectStore
metadata:
name: azure-store
spec:
configuration:
destinationPath: "<destination path here>"
azureCredentials:
useDefaultAzureCredentials: true
[...]
```
### Access Key, SAS Token, or Connection String
Store credentials in a Kubernetes secret: