Compare commits

...

5 Commits

Author SHA1 Message Date
Rasmus Kock Thygesen
1ae9d01f49
Merge 2a12a52211 into 0111ed9f4d 2026-04-21 12:35:06 +02:00
Jonathan Gonzalez V.
0111ed9f4d
chore: update serialize-javascript package to >= 7.0.5 (#866)
Some checks failed
Deploy Docusaurus to GitHub Pages / build (push) Failing after 4s
Deploy Docusaurus to GitHub Pages / deploy (push) Has been skipped
release-please / release-please (push) Failing after 2s
Signed-off-by: Jonathan Gonzalez V. <jonathan.gonzalez@enterprisedb.com>
2026-04-21 10:44:56 +02:00
renovate[bot]
742a0c0810
chore(deps): lock file maintenance (#864)
Some checks failed
Deploy Docusaurus to GitHub Pages / build (push) Failing after 3s
Deploy Docusaurus to GitHub Pages / deploy (push) Has been skipped
release-please / release-please (push) Failing after 2s
This PR contains the following updates:

| Update | Change |
|---|---|
| lockFileMaintenance | All locks refreshed |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the [Dependency
Dashboard](../issues/7) for more information.

🔧 This Pull Request updates lock files to use the latest dependency
versions.

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "before 4am on monday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Never, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/cloudnative-pg/plugin-barman-cloud).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMjMuOCIsInVwZGF0ZWRJblZlciI6IjQzLjEyMy44IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJhdXRvbWF0ZWQiLCJuby1pc3N1ZSJdfQ==-->

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-20 17:13:03 +02:00
Rasmus Kock Thygesen
2a12a52211
Merge branch 'main' into rkth/azure-workload-identity 2026-04-14 15:29:07 +02:00
rkthtrifork
2a70f54ef8
Add Azure workload identity support
Signed-off-by: rkthtrifork <rkth@trifork.com>
2026-03-17 09:29:04 +01:00
5 changed files with 260 additions and 38 deletions

View File

@ -145,18 +145,25 @@ func (impl LifecycleImplementation) reconcileJob(
return nil, err
}
useAzureWorkloadIdentity, err := impl.collectAzureWorkloadIdentityUsage(ctx, pluginConfiguration)
if err != nil {
return nil, err
}
return reconcileJob(ctx, cluster, request, sidecarConfiguration{
env: env,
certificates: certificates,
resources: resources,
env: env,
certificates: certificates,
resources: resources,
useAzureWorkloadIdentity: useAzureWorkloadIdentity,
})
}
type sidecarConfiguration struct {
env []corev1.EnvVar
certificates []corev1.VolumeProjection
resources corev1.ResourceRequirements
additionalArgs []string
env []corev1.EnvVar
certificates []corev1.VolumeProjection
resources corev1.ResourceRequirements
additionalArgs []string
useAzureWorkloadIdentity bool
}
func reconcileJob(
@ -243,11 +250,17 @@ func (impl LifecycleImplementation) reconcilePod(
return nil, err
}
useAzureWorkloadIdentity, err := impl.collectAzureWorkloadIdentityUsage(ctx, pluginConfiguration)
if err != nil {
return nil, err
}
return reconcileInstancePod(ctx, cluster, request, pluginConfiguration, sidecarConfiguration{
env: env,
certificates: certificates,
resources: resources,
additionalArgs: additionalArgs,
env: env,
certificates: certificates,
resources: resources,
additionalArgs: additionalArgs,
useAzureWorkloadIdentity: useAzureWorkloadIdentity,
})
}
@ -301,6 +314,25 @@ func (impl LifecycleImplementation) collectAdditionalInstanceArgs(
return nil, nil
}
func (impl LifecycleImplementation) collectAzureWorkloadIdentityUsage(
ctx context.Context,
pluginConfiguration *config.PluginConfiguration,
) (bool, error) {
for _, objectKey := range pluginConfiguration.GetReferredBarmanObjectsKey() {
var objectStore barmancloudv1.ObjectStore
if err := impl.Client.Get(ctx, objectKey, &objectStore); err != nil {
return false, fmt.Errorf("while getting object store %s: %w", objectKey.String(), err)
}
if objectStore.Spec.Configuration.Azure != nil &&
objectStore.Spec.Configuration.Azure.UseDefaultAzureCredentials {
return true, nil
}
}
return false, nil
}
func reconcileInstancePod(
ctx context.Context,
cluster *cnpgv1.Cluster,
@ -379,6 +411,13 @@ func reconcilePodSpec(
},
)
if config.useAzureWorkloadIdentity {
envs = append(envs, corev1.EnvVar{
Name: "AZURE_FEDERATED_TOKEN_FILE",
Value: azureFederatedTokenFilePath,
})
}
envs = append(envs, config.env...)
baseProbe := &corev1.Probe{
@ -466,6 +505,34 @@ func reconcilePodSpec(
spec.Volumes = removeVolume(spec.Volumes, barmanCertificatesVolumeName)
}
if config.useAzureWorkloadIdentity {
sidecarTemplate.VolumeMounts = ensureVolumeMount(
sidecarTemplate.VolumeMounts,
corev1.VolumeMount{
Name: azureFederatedTokenVolumeName,
MountPath: azureFederatedTokenMountPath,
ReadOnly: true,
},
)
spec.Volumes = ensureVolume(spec.Volumes, corev1.Volume{
Name: azureFederatedTokenVolumeName,
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
Sources: []corev1.VolumeProjection{
{
ServiceAccountToken: &corev1.ServiceAccountTokenProjection{
Path: azureFederatedTokenFileName,
Audience: azureFederatedTokenAudience,
ExpirationSeconds: ptr.To[int64](azureFederatedTokenExpirationSeconds),
},
},
},
},
},
})
}
if err := injectPluginSidecarPodSpec(spec, &sidecarTemplate, mainContainerName); err != nil {
return err
}
@ -473,6 +540,15 @@ func reconcilePodSpec(
return nil
}
const (
azureFederatedTokenVolumeName = "azure-identity-token"
azureFederatedTokenMountPath = "/var/run/secrets/azure/tokens"
azureFederatedTokenFileName = "azure-identity-token"
azureFederatedTokenFilePath = azureFederatedTokenMountPath + "/" + azureFederatedTokenFileName
azureFederatedTokenAudience = "api://AzureADTokenExchange"
azureFederatedTokenExpirationSeconds = 3600
)
// TODO: move to machinery once the logic is finalized
// InjectPluginVolumePodSpec injects the plugin volume into a CNPG Pod spec.

View File

@ -22,6 +22,7 @@ package operator
import (
"encoding/json"
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
"github.com/cloudnative-pg/cnpg-i/pkg/lifecycle"
@ -387,6 +388,134 @@ var _ = Describe("LifecycleImplementation", func() {
Expect(args).To(Equal([]string{"--log-level=info"}))
})
})
Describe("collectAzureWorkloadIdentityUsage", func() {
It("returns true when any referred object store uses default Azure credentials", func(ctx SpecContext) {
ns := "test-ns"
cluster := &cnpgv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "c", Namespace: ns}}
pc := &config.PluginConfiguration{
Cluster: cluster,
BarmanObjectName: "primary-store",
RecoveryBarmanObjectName: "recovery-store",
}
primaryStore := &barmancloudv1.ObjectStore{
ObjectMeta: metav1.ObjectMeta{Name: pc.BarmanObjectName, Namespace: ns},
Spec: barmancloudv1.ObjectStoreSpec{
Configuration: barmanapi.BarmanObjectStoreConfiguration{
BarmanCredentials: barmanapi.BarmanCredentials{
Azure: &barmanapi.AzureCredentials{UseDefaultAzureCredentials: true},
},
},
},
}
recoveryStore := &barmancloudv1.ObjectStore{
ObjectMeta: metav1.ObjectMeta{Name: pc.RecoveryBarmanObjectName, Namespace: ns},
}
cli := buildClientFunc(primaryStore, recoveryStore).Build()
impl := LifecycleImplementation{Client: cli}
useWorkloadIdentity, err := impl.collectAzureWorkloadIdentityUsage(ctx, pc)
Expect(err).NotTo(HaveOccurred())
Expect(useWorkloadIdentity).To(BeTrue())
})
It("returns false when none of the referred object stores use default Azure credentials", func(ctx SpecContext) {
ns := "test-ns"
cluster := &cnpgv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "c", Namespace: ns}}
pc := &config.PluginConfiguration{
Cluster: cluster,
BarmanObjectName: "primary-store",
}
primaryStore := &barmancloudv1.ObjectStore{
ObjectMeta: metav1.ObjectMeta{Name: pc.BarmanObjectName, Namespace: ns},
Spec: barmancloudv1.ObjectStoreSpec{
Configuration: barmanapi.BarmanObjectStoreConfiguration{
BarmanCredentials: barmanapi.BarmanCredentials{
Azure: &barmanapi.AzureCredentials{InheritFromAzureAD: true},
},
},
},
}
cli := buildClientFunc(primaryStore).Build()
impl := LifecycleImplementation{Client: cli}
useWorkloadIdentity, err := impl.collectAzureWorkloadIdentityUsage(ctx, pc)
Expect(err).NotTo(HaveOccurred())
Expect(useWorkloadIdentity).To(BeFalse())
})
})
})
var _ = Describe("reconcilePodSpec", func() {
It("injects the Azure federated token volume and env when workload identity is enabled", func() {
cluster := &cnpgv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "cluster-1", Namespace: "ns-1"}}
spec := &corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "postgres",
Env: []corev1.EnvVar{
{Name: "AZURE_CLIENT_ID", Value: "client-id"},
},
},
},
}
err := reconcilePodSpec(
cluster,
spec,
"postgres",
corev1.Container{Args: []string{"instance"}},
sidecarConfiguration{useAzureWorkloadIdentity: true},
)
Expect(err).NotTo(HaveOccurred())
Expect(spec.Volumes).To(ContainElement(HaveField("Name", azureFederatedTokenVolumeName)))
Expect(spec.InitContainers).To(HaveLen(1))
Expect(spec.InitContainers[0].Env).To(ContainElement(corev1.EnvVar{
Name: "AZURE_FEDERATED_TOKEN_FILE",
Value: azureFederatedTokenFilePath,
}))
Expect(spec.InitContainers[0].Env).To(ContainElement(corev1.EnvVar{
Name: "AZURE_CLIENT_ID",
Value: "client-id",
}))
Expect(spec.InitContainers[0].VolumeMounts).To(ContainElement(corev1.VolumeMount{
Name: azureFederatedTokenVolumeName,
MountPath: azureFederatedTokenMountPath,
ReadOnly: true,
}))
})
It("does not override an existing federated token file env", func() {
cluster := &cnpgv1.Cluster{ObjectMeta: metav1.ObjectMeta{Name: "cluster-1", Namespace: "ns-1"}}
spec := &corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "postgres",
Env: []corev1.EnvVar{
{Name: "AZURE_FEDERATED_TOKEN_FILE", Value: "/custom/token"},
},
},
},
}
err := reconcilePodSpec(
cluster,
spec,
"postgres",
corev1.Container{Args: []string{"instance"}},
sidecarConfiguration{useAzureWorkloadIdentity: true},
)
Expect(err).NotTo(HaveOccurred())
Expect(spec.InitContainers).To(HaveLen(1))
Expect(spec.InitContainers[0].Env).To(ContainElement(corev1.EnvVar{
Name: "AZURE_FEDERATED_TOKEN_FILE",
Value: "/custom/token",
}))
Expect(spec.InitContainers[0].Env).NotTo(ContainElement(corev1.EnvVar{
Name: "AZURE_FEDERATED_TOKEN_FILE",
Value: azureFederatedTokenFilePath,
}))
})
})
var _ = Describe("Volume utilities", func() {

View File

@ -272,9 +272,10 @@ flow, which uses [`DefaultAzureCredential`](https://learn.microsoft.com/en-us/py
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
2. **Workload Identity** — Uses `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and a federated service account token
3. **Managed Identity** — Uses the managed identity assigned to the pod
4. **Azure CLI** — Uses credentials from the Azure CLI if available
5. **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.
@ -295,6 +296,30 @@ spec:
[...]
```
When `useDefaultAzureCredentials: true` is set, the plugin sidecar projects a
service account token with the Azure workload identity audience and exposes it
as `AZURE_FEDERATED_TOKEN_FILE`. If your platform does not already inject
`AZURE_CLIENT_ID` and `AZURE_TENANT_ID`, you can provide them through
`.spec.instanceSidecarConfiguration.env`:
```yaml
apiVersion: barmancloud.cnpg.io/v1
kind: ObjectStore
metadata:
name: azure-store
spec:
configuration:
destinationPath: "<destination path here>"
azureCredentials:
useDefaultAzureCredentials: true
instanceSidecarConfiguration:
env:
- name: AZURE_CLIENT_ID
value: "<managed-identity-client-id>"
- name: AZURE_TENANT_ID
value: "<tenant-id>"
```
### Access Key, SAS Token, or Connection String
Store credentials in a Kubernetes secret:

View File

@ -43,9 +43,10 @@
]
},
"engines": {
"node": ">=18.0"
"node": ">=20.0.0"
},
"resolutions": {
"webpackbar": "^7.0.0"
"webpackbar": "^7.0.0",
"serialize-javascript": ">=7.0.5"
}
}

View File

@ -3389,12 +3389,12 @@ array-union@^2.1.0:
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
asn1js@^3.0.6:
version "3.0.7"
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.7.tgz#15f1f2f59e60f80d5b43ef14047a294a969f824f"
integrity sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==
version "3.0.10"
resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.10.tgz#df26c874c8a8b41ca605efea47b2ad07551013dd"
integrity sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==
dependencies:
pvtsutils "^1.3.6"
pvutils "^1.1.3"
pvutils "^1.1.5"
tslib "^2.8.1"
astring@^1.8.0:
@ -5849,9 +5849,9 @@ json5@^2.1.2, json5@^2.2.3:
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonfile@^6.0.1:
version "6.2.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62"
integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==
version "6.2.1"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.1.tgz#b6e31717f22cc37330b081ce0051ed5de53af2f6"
integrity sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==
dependencies:
universalify "^2.0.0"
optionalDependencies:
@ -7832,7 +7832,7 @@ pvtsutils@^1.3.6:
dependencies:
tslib "^2.8.1"
pvutils@^1.1.3:
pvutils@^1.1.3, pvutils@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.5.tgz#84b0dea4a5d670249aa9800511804ee0b7c2809c"
integrity sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==
@ -7854,13 +7854,6 @@ quick-lru@^5.1.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
dependencies:
safe-buffer "^5.1.0"
range-parser@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
@ -8296,7 +8289,7 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@ -8402,12 +8395,10 @@ send@~0.19.0, send@~0.19.1:
range-parser "~1.2.1"
statuses "~2.0.2"
serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
version "6.0.2"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
dependencies:
randombytes "^2.1.0"
serialize-javascript@>=7.0.5, serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
version "7.0.5"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.0.5.tgz#c798cc0552ffbb08981914a42a8756e339d0d5b1"
integrity sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==
serve-handler@^6.1.7:
version "6.1.7"