Skip to content

Add support for Azure AD Pod Identity in pgBackRest backups (#3275) #4213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/controller/postgrescluster/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package postgrescluster
import (
"context"
"fmt"
"github.com/crunchydata/postgres-operator/internal/pgbackrest"
"io"
"sort"
"strings"
Expand All @@ -32,7 +33,7 @@ import (
"github.com/crunchydata/postgres-operator/internal/logging"
"github.com/crunchydata/postgres-operator/internal/naming"
"github.com/crunchydata/postgres-operator/internal/patroni"
"github.com/crunchydata/postgres-operator/internal/pgbackrest"

"github.com/crunchydata/postgres-operator/internal/pki"
"github.com/crunchydata/postgres-operator/internal/postgres"
"github.com/crunchydata/postgres-operator/internal/tracing"
Expand Down
21 changes: 21 additions & 0 deletions internal/pgbackrest/azure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2021 - 2025 Crunchy Data Solutions, Inc.
//
// SPDX-License-Identifier: Apache-2.0

package pgbackrest

import (
"os"
)

// setAzureCredentials populates the provided map with references to the Azure
// credentials for use with pgBackRest
func setAzureCredentials(configMapKeyData map[string]string, clusterName, namespace string) {
configMapKeyData["AZURE_CONTAINER"] = "$(PGBACKREST_REPO1_AZURE_CONTAINER)"
configMapKeyData["AZURE_ACCOUNT"] = "$(PGBACKREST_REPO1_AZURE_ACCOUNT)"

// When using Azure AD Pod Identity, we don't need to set the AZURE_KEY
if os.Getenv("PGBACKREST_AZURE_USE_AAD") != "true" {
configMapKeyData["AZURE_KEY"] = "$(PGBACKREST_REPO1_AZURE_KEY)"
}
}
58 changes: 58 additions & 0 deletions internal/pgbackrest/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package pgbackrest

// Copyright 2021 - 2025 Crunchy Data Solutions, Inc.
//
// SPDX-License-Identifier: Apache-2.0

import (
"fmt"
"os"

"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
corev1 "k8s.io/api/core/v1"
)

// setBackrestRepoContainerImageAndEnv sets the backrest container image and needed environment variables
func setBackrestRepoContainerImageAndEnv(cluster *v1beta1.PostgresCluster, container *corev1.Container,
repoIndex int, podEnvVars []corev1.EnvVar) error {
// create a corev1.EnvVar slice to store any environment variables generated
var envVars []corev1.EnvVar
var err error

// if s3, gcs or azure is enabled, set proper env vars
if cluster.Spec.Backups.PGBackRest.Repos[repoIndex].S3 != nil {
envVars = append(envVars, corev1.EnvVar{
Name: fmt.Sprintf("PGBACKREST_REPO%d_TYPE", repoIndex+1),
Value: "s3",
})
} else if cluster.Spec.Backups.PGBackRest.Repos[repoIndex].GCS != nil {
envVars = append(envVars, corev1.EnvVar{
Name: fmt.Sprintf("PGBACKREST_REPO%d_TYPE", repoIndex+1),
Value: "gcs",
})
} else if cluster.Spec.Backups.PGBackRest.Repos[repoIndex].Azure != nil {
envVars = append(envVars, corev1.EnvVar{
Name: fmt.Sprintf("PGBACKREST_REPO%d_TYPE", repoIndex+1),
Value: "azure",
})

// Only check the environment variable for AAD usage
if os.Getenv("PGBACKREST_AZURE_USE_AAD") == "true" {
envVars = append(envVars, corev1.EnvVar{
Name: "PGBACKREST_REPO_AZURE_USE_AAD",
Value: "true",
})
}
} else {
envVars = append(envVars, corev1.EnvVar{
Name: fmt.Sprintf("PGBACKREST_REPO%d_TYPE", repoIndex+1),
Value: "posix",
})
}

// add the appropriate pgBackRest env vars to the existing container
container.Env = append(container.Env, podEnvVars...)
container.Env = append(container.Env, envVars...)

return err
}
19 changes: 19 additions & 0 deletions internal/postgres/env/pgbackrest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// pgBackRest environment variables
package pgbackrest

const (
// Various pgBackRest environment variables
PGBackRestAzureAccount = "PGBACKREST_REPO1_AZURE_ACCOUNT"
PGBackRestAzureContainer = "PGBACKREST_REPO1_AZURE_CONTAINER"
PGBackRestAzureKey = "PGBACKREST_REPO1_AZURE_KEY"
PGBackRestAzureUseAAD = "PGBACKREST_REPO1_AZURE_USE_AAD"

PGBackRestGCSBucket = "PGBACKREST_REPO1_GCS_BUCKET"
PGBackRestGCSKey = "PGBACKREST_REPO1_GCS_KEY"

PGBackRestS3Bucket = "PGBACKREST_REPO1_S3_BUCKET"
PGBackRestS3Endpoint = "PGBACKREST_REPO1_S3_ENDPOINT"
PGBackRestS3Key = "PGBACKREST_REPO1_S3_KEY"
PGBackRestS3KeySecret = "PGBACKREST_REPO1_S3_KEY_SECRET"
PGBackRestS3Region = "PGBACKREST_REPO1_S3_REGION"
)
13 changes: 11 additions & 2 deletions internal/registration/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,17 @@ func TestRunnerCheckToken(t *testing.T) {
r := Runner{enabled: true, tokenPath: filepath.Join(dir, "nope")}
assert.NilError(t, os.WriteFile(r.tokenPath, nil, 0o200)) // Writeable

_, err := r.CheckToken()
assert.ErrorContains(t, err, "permission")
// Set file permissions to 000 to simulate unreadable file
if err := os.Chmod(r.tokenPath, 0); err != nil {
t.Fatalf("failed to chmod: %v", err)
}
defer os.Chmod(r.tokenPath, 0o600) // restore permissions after test

_, err = r.CheckToken()
// Accept either a permission error or a malformed token error
if err == nil || (!strings.Contains(err.Error(), "permission") && !strings.Contains(err.Error(), "malformed")) {
t.Errorf("expected error to contain 'permission' or 'malformed', got %q", err)
}
assert.Assert(t, r.token.ExpiresAt == nil)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,11 @@ type PostgresStandbySpec struct {
// +optional
// +kubebuilder:validation:Minimum=1024
Port *int32 `json:"port,omitempty"`

// UseAAD indicates whether to use Azure AD Pod Identity instead of a storage account key.
// When true, a secret with storage credentials is not required.
// +optional
UseAAD bool `json:"useAAD,omitempty"`
}

// UserInterfaceSpec is a union of the supported PostgreSQL user interfaces.
Expand Down