garage-storage-controller

module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 8, 2026 License: Apache-2.0

README

Storage controller for Garage (S3-compatible) clusters.

codecov

Overview

garage-storage-controller handles bucket and access key management for Garage storage clusters.

Project status

This project is in beta and the core functionality is present. Due to the limited scope and complexity, no major changes are expected.

Important considerations:

  • CRDs are still alpha and the API might need to change.
  • The controller focuses on the Garage admin API for its functionality.
    • Open an issue if an important bucket config option should be exposed.
  • Bucket configuration is primarily managed through the S3 API

Limitations:

  • Existing buckets currently cannot be imported.
  • Accessing buckets outside the 'owning' namespace is not allowed.
  • ConfigMap drift is hard to detect due to the current RBAC & security model.

Quick start

apiVersion: garage.getclustered.net/v1alpha1
kind: Bucket
metadata:
  name: bucket-sample
spec:
  name: foo-global-name
  maxSize: 10Gi

---
apiVersion: garage.getclustered.net/v1alpha1
kind: AccessKey
metadata:
  name: accesskey-sample
spec:
  secretName: foo-bucket-access-rw

---
apiVersion: garage.getclustered.net/v1alpha1
kind: AccessPolicy
metadata:
  name: accesspolicy-sample
spec:
  accessKey: accesskey-sample
  bucket: bucket-sample
  permissions:
    read: true
    write: true
    owner: false

This will create a bucket and an access key on Garage. Permissions will be set via the admin API.

Bucket configuration and access key will be stored in Kubernetes objects:

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: bucket-sample
data:
  bucket-name: "foo-global-name-abcd1234"
  s3-endpoint: "https://s3.garage.foo.net"

---
apiVersion: v1
kind: Secret
metadata:
  name: foo-bucket-access-rw
data:
  access-key-id: "ABC..."
  secret-access-key: "..."

CRDs

  • Bucket: Creates S3 buckets on the Garage cluster
  • AccessKey: Generate credentials and store them as namespace secrets
  • AccessPolicy: Control which keys can access a bucket

Objectives and scope

Creating and managing storage buckets through Kubernetes API resources should be simple:

  • Focus on the common use cases
  • Create buckets and access keys for namespace workloads
  • Keep this controller safe and easy to operate

Note: Deleting Bucket API resources will never remove the actual buckets on the storage backend. Data loss is not boring. And we aim for boring here.

Accessing buckets in workloads

The S3 API address and the access key are available in the aforementioned ConfigMap and Secret in the namespace.

Mounting bucket details as environment variables

The configmap as well as the secret can be consumed as environment variables:

kind: Pod
spec:
  containers:
  - name: workload
    env:
    # s3 client config:
    - name: S3_ENDPOINT
      valueFrom:
        configMapKeyRef:
          name: bucket-sample
          key: s3-endpoint
    # bucket config values:
    - name: FOO_BUCKET_NAME
      valueFrom:
        configMapKeyRef:
          name: bucket-sample
          key: bucket-name
    - name: FOO_ACCESS_KEY
      valueFrom:
        secretKeyRef:
          name: foo-bucket-access-rw
          key: access-key-id
    - name: FOO_ACCESS_KEY_SECRET
      valueFrom:
        secretKeyRef:
          name: foo-bucket-access-rw
          key: secret-access-key

Overriding the ConfigMap name

By default the controller will create a ConfigMap with the same name as the Bucket.

If you need a different name e.g. in case of a conflict, set configMapName:

apiVersion: garage.getclustered.net/v1alpha1
kind: Bucket
metadata:
  name: bucket-sample
spec:
  name: foo-global-name
  configMapName: bucket-sample-config
  maxSize: 10Gi

Install

Configuration

The controller expects the following configuration to be available as environment variables.

Env variable Description
GARAGE_API_ENDPOINT Endpoint address of the Garage admin API.
GARAGE_API_TOKEN Token used to authenticate requests to the Garage admin API.
GARAGE_S3_API_ENDPOINT Endpoint address of the S3 client API provided to workloads.

See the Garage Administration API docs on how to obtain an admin token.

Install with Helm

Chart values

Provide required configuration values:

# values.yaml
config:
  garageApiEndpoint: "http://garage:3903"
  garageS3ApiEndpoint: "http://garage:3900"

One of existingSecret and apiToken must be set.

Existing secret:

Create a secret containing the admin token:

apiVersion: v1
kind: Secret
metadata:
  name: garage-api-token
stringData:
  api-token: "***"

Set the value accordingly

existingSecret: "garage-api-token"

Alternatively, pass the API token directly as a chart value. A secret with a default name will be created:

helm install ... --set apiToken="$garage_api_token"

Install

helm install garage-storage-controller \
  oci://ghcr.io/bmarinov/charts/garage-storage-controller \
  -n garage-controller-system --create-namespace \
  -f myvalues.yaml

Enrolling namespaces

The controller watches custom resources cluster-wide. It requires explicit permission to read and write ConfigMaps and Secrets in tenant namespaces. Without this, reconciliation will fail:

{"error": "configmaps \"...\" is forbidden: User \"system:serviceaccount:garage-controller-system:...\" cannot get resource \"configmaps\" in API group \"\" in the namespace \"default\""}

After installation, the ServiceAccount name is printed in the Helm notes. Use it in the RoleBinding subject below.
The following example enrolls the default namespace:

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: garage-controller-namespace-access
  namespace: default # target namespace
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["create", "delete", "get", "list", "patch", "update"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["create", "delete", "get", "list", "patch", "update"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: garage-controller-namespace-access-rolebinding
  namespace: default # target namespace
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: garage-controller-namespace-access
subjects:
- kind: ServiceAccount
  name: garage-storage-controller # controller ServiceAccount
  namespace: garage-controller-system # controller namespace 

Manual installation

Configuration

Create a configmap and a secret in the controller namespace and reference them in the deployment manifest.

Examples can be found in config/env:

Custom resources

Output CRD manifests to a file:

kubectl kustomize ./config/install/crds -o crds.yaml

Or install directly in the cluster:

kubectl apply -k ./config/install/crds

RBAC

Controller Role and ServiceAccount
kubectl kustomize ./config/install/rbac -o rbac.yaml

This customization creates:

  • the controller namespace and a service account
  • cluster roles for CRDs
  • leader election roles
Namespaced / tenant roles

Managing resources in a given namespace requires permissions over Secrets and ConfigMap resources.

Example with the included default namespace overlay:

kubectl kustomize ./config/rbac/namespaces/default -o default-ns-access.yaml

This will create a role and a role binding in one specific namespace.
It is not recommended, but you can also provide cluster-wide access to configmaps and secrets.

Deployment

The controller kustomization includes an example deployment manifest:

kubectl kustomize ./config/install/controller -o deployment.yaml

The deployment will remain in status CreateContainerConfigError until the expected ConfigMap and Secret are created. See Configuration for more details and an example.

Full manifest

Manifests can also be rendered to a single file:

kubectl kustomize ./config/install/full > dist/install.yaml

Permissions model and security

The controller needs cluster-level permissions for its CRDs. ConfigMap and Secret access can be restricted to specific namespaces.

CRDs

The controller manages the following custom resources in the garage.getclustered.net group:

  • accesskeys
  • accesspolicies
  • buckets

See config/rbac/role.yaml for the ClusterRole definition.

ConfigMaps and Secrets

The controller stores bucket connection details in ConfigMaps and S3 credentials in Secrets. These resources are created in the same namespace as the Bucket and AccessKey resources.

Grant access and enroll a namespace (example):

kubectl apply -k "config/rbac/namespaces/${namespace}"

See config/rbac/base/role_namespace_access.yaml for the Role definition.

Note: The controller does not need cluster-wide access to ConfigMaps or Secrets. Use namespace roles.

Development

Project setup

Scaffolding done with kubebuilder. See docs for more info.

Running tests

E2E

Run suite with make test-e2e. Requires Kind to be installed and available.:

  • Download a specific release:
version="v0.30.0"
[ "$(uname -m)" = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/$version/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
  • Or latest from Homebrew: brew install kind

Debugging

E2E Tests

Setup environment with make setup-test-e2e and launch tests in e2e packge.

VS Code launch profile:

{
  "configurations": [
    {
        "name": "Debug E2E Tests",
        "type": "go",
        "request": "launch",
        "mode": "test",
        "program": "${workspaceFolder}/test/e2e",
        "env": {
            "KIND_CLUSTER": "garage-storage-controller-test-e2e",
            "RUN_E2E": "true"
        }
    }
  ]
}

Directories

Path Synopsis
api
v1alpha1
+kubebuilder:validation:Required
+kubebuilder:validation:Required
internal
s3
test
e2e

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL