zoobzio January 23, 2026 Edit this page

GCP Secret Manager

The gcpsm package provides a fig.SecretProvider backed by Google Cloud Secret Manager.

Installation

go get github.com/zoobzio/fig/gcpsm

Quick Start

import (
    "context"
    "log"

    "github.com/zoobzio/fig"
    "github.com/zoobzio/fig/gcpsm"
)

type Config struct {
    DBPassword string `secret:"database-password"`
    APIKey     string `secret:"api-key"`
}

func main() {
    ctx := context.Background()

    provider, err := gcpsm.New(ctx, "my-project-id")
    if err != nil {
        log.Fatal(err)
    }

    var cfg Config
    if err := fig.LoadContext(ctx, &cfg, provider); err != nil {
        log.Fatal(err)
    }
}

Configuration

Authentication

The provider uses Application Default Credentials (ADC):

  1. GOOGLE_APPLICATION_CREDENTIALS environment variable
  2. User credentials from gcloud auth application-default login
  3. Attached service account (GCE, Cloud Run, GKE)

Project ID

The project ID is required:

provider, err := gcpsm.New(ctx, "my-project-id")

Get project ID from metadata server (on GCP):

import "cloud.google.com/go/compute/metadata"

projectID, _ := metadata.ProjectID()
provider, _ := gcpsm.New(ctx, projectID)

Key Format

Keys follow the format secret-name or secret-name:json-field:

Plain Text Secrets

// Secret name: api-key
// Value: sk_live_12345
APIKey string `secret:"api-key"`

JSON Secrets

// Secret name: database-credentials
// Value: {"username": "admin", "password": "secret"}
DBUser     string `secret:"database-credentials:username"`
DBPassword string `secret:"database-credentials:password"`

Creating Secrets

# Create secret
gcloud secrets create api-key --replication-policy="automatic"

# Add version
echo -n "sk_live_12345" | gcloud secrets versions add api-key --data-file=-

# JSON secret
echo -n '{"username":"admin","password":"secret"}' | \
    gcloud secrets versions add database-credentials --data-file=-

API Reference

New

func New(ctx context.Context, project string, opts ...Option) (*Provider, error)

Creates a provider using Application Default Credentials.

Parameters:

  • ctx — context for credential discovery
  • project — GCP project ID
  • opts — optional configuration

Returns: (*Provider, error) — error if credentials cannot be found.

Options

WithHTTPClient

func WithHTTPClient(client *http.Client) Option

Sets a custom HTTP client. Useful for testing or custom transport configuration.

Close

func (p *Provider) Close() error

No-op, retained for interface compatibility. The provider uses HTTP/1.1 and doesn't require cleanup.

Secret Versions

The provider always fetches the latest version. For specific versions, implement a wrapper:

type VersionedProvider struct {
    inner   *gcpsm.Provider
    version string  // e.g., "1", "2", or "latest"
}

func (p *VersionedProvider) Get(ctx context.Context, key string) (string, error) {
    // Modify key to include version
    versionedKey := fmt.Sprintf("%s@%s", key, p.version)
    // Would require modifying gcpsm to support this
    return p.inner.Get(ctx, key)
}

IAM Permissions

Required role: roles/secretmanager.secretAccessor

gcloud secrets add-iam-policy-binding api-key \
    --member="serviceAccount:my-service@my-project.iam.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"

Or at project level:

gcloud projects add-iam-policy-binding my-project \
    --member="serviceAccount:my-service@my-project.iam.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"

Workload Identity (GKE)

For GKE with Workload Identity:

# Create KSA
kubectl create serviceaccount my-app

# Bind to GSA
gcloud iam service-accounts add-iam-policy-binding \
    my-service@my-project.iam.gserviceaccount.com \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:my-project.svc.id.goog[default/my-app]"

# Annotate KSA
kubectl annotate serviceaccount my-app \
    iam.gke.io/gcp-service-account=my-service@my-project.iam.gserviceaccount.com

The provider automatically uses the workload identity when running in GKE.

Error Handling

The provider returns fig.ErrSecretNotFound when:

  • The secret doesn't exist
  • The secret version doesn't exist
  • The requested JSON field doesn't exist
  • The JSON field is not a string

Other errors (network, permissions) are returned as-is.

Regional vs Global Replication

The provider works with both replication policies. The latest version is fetched regardless of replication strategy.

For multi-region applications, consider using automatic replication:

gcloud secrets create my-secret --replication-policy="automatic"

Next Steps