Files
cephrgw-operator/internal/ceph/policy_test.go
benvin 1ea1713d6e Initial cephrgw-operator: Ceph RGW buckets & keys via dashboard API
Adds a Kubernetes operator that provisions Ceph RGW (S3) buckets and
access keys declaratively through the Ceph manager dashboard REST API.

Three CRDs in group ceph.unkin.net/v1alpha1:
- ObjectStoreUser: creates an RGW user, delivers its key pair to a Secret
- Bucket: creates an S3 bucket owned by an ObjectStoreUser; owns the
  bucket's aggregate S3 policy (union of all BucketAccess grants)
- BucketAccess: grants read-only/read-write/full access, provisioning a
  dedicated user (or reusing a referenced one) and delivering RW/RO keys

The internal/ceph client wraps the dashboard /api/auth, /api/rgw/user and
/api/rgw/bucket endpoints with lazy token auth and re-auth on 401. Bucket
policies are rendered deterministically and applied via the bucket
policy API (Reef 18.2+). Credentials come from the cephrgw-credentials
Secret via env. Includes generated CRDs/RBAC, samples, kind manifests,
Woodpecker CI, and docs/ceph-setup.md covering the required Ceph
dashboard account, RGW wiring and permissions.
2026-07-18 00:07:22 +10:00

91 lines
2.2 KiB
Go

package ceph
import (
"encoding/json"
"strings"
"testing"
)
func TestBuildBucketPolicyEmpty(t *testing.T) {
got, err := BuildBucketPolicy("data", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "" {
t.Fatalf("expected empty policy for no grants, got %q", got)
}
}
func TestBuildBucketPolicyDeterministic(t *testing.T) {
a, err := BuildBucketPolicy("data", []Grant{
{UID: "reader", Level: LevelReadOnly},
{UID: "writer", Level: LevelReadWrite},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
b, err := BuildBucketPolicy("data", []Grant{
{UID: "writer", Level: LevelReadWrite},
{UID: "reader", Level: LevelReadOnly},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if a != b {
t.Fatalf("policy is order-dependent:\n a=%s\n b=%s", a, b)
}
}
func TestBuildBucketPolicyStructure(t *testing.T) {
raw, err := BuildBucketPolicy("data", []Grant{
{UID: "reader", Level: LevelReadOnly},
{UID: "admin", Level: LevelFull},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var doc struct {
Version string `json:"Version"`
Statement []struct {
Effect string `json:"Effect"`
Principal map[string][]string `json:"Principal"`
Action []string `json:"Action"`
Resource []string `json:"Resource"`
} `json:"Statement"`
}
if err := json.Unmarshal([]byte(raw), &doc); err != nil {
t.Fatalf("policy is not valid JSON: %v\n%s", err, raw)
}
if doc.Version != "2012-10-17" {
t.Fatalf("unexpected version %q", doc.Version)
}
// read-only -> two statements (bucket + object); full -> one statement.
if len(doc.Statement) != 3 {
t.Fatalf("expected 3 statements, got %d", len(doc.Statement))
}
var sawFullWildcard, sawReaderPrincipal bool
for _, s := range doc.Statement {
if s.Effect != "Allow" {
t.Fatalf("expected Allow effect, got %q", s.Effect)
}
for _, a := range s.Action {
if a == "s3:*" {
sawFullWildcard = true
}
}
for _, p := range s.Principal["AWS"] {
if strings.HasSuffix(p, "user/reader") {
sawReaderPrincipal = true
}
}
}
if !sawFullWildcard {
t.Fatal("full grant did not produce an s3:* action")
}
if !sawReaderPrincipal {
t.Fatal("reader principal ARN missing")
}
}