Add fine-grained bucket access: paths, actions, conditions, raw
The BucketAccess model only offered three coarse levels (read-only/read-write/ full) applied to the whole bucket. Real grants often need to be scoped to a key prefix, limited to a source network or TLS, restricted to specific actions, or expressed as an arbitrary S3 statement. RGW (Reef 18.2+/Squid) honours the S3 bucket-policy features to do all of this; expose them on BucketAccess while keeping the level as the ergonomic default. - add BucketAccess spec fields: paths (key-prefix scoping), actions (action override), conditions (sourceIPs + secureTransportOnly), rawStatements (arbitrary S3 statements with the principal injected) - extend ceph.Grant + BuildBucketPolicy to render prefixed object resources, custom-action statements, S3 condition blocks, and raw statements, keeping output deterministic (sorted, stable sids) - translate the new spec fields into grants in the Bucket controller and fingerprint grants so distinct fine-grained BucketAccess objects no longer collapse on UID+level alone - regenerate deepcopy + CRDs; add config/samples/04-access-fine-grained.yaml - cover paths, action override, conditions, raw statements and determinism in policy_test.go; document the fields in the README Claude-Session: https://claude.ai/code/session_016CEncETbf8cvy1PhsHfFHM
This commit is contained in:
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -176,17 +177,52 @@ func (r *BucketReconciler) collectGrants(ctx context.Context, namespace, bucketR
|
||||
if ba.Status.UID == "" {
|
||||
continue
|
||||
}
|
||||
key := ba.Status.UID + "|" + string(ba.Spec.Level)
|
||||
g := grantFromAccess(ba.Status.UID, ba)
|
||||
key := grantKey(g)
|
||||
if _, dup := seen[key]; dup {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
principals[ba.Status.UID] = struct{}{}
|
||||
grants = append(grants, ceph.Grant{UID: ba.Status.UID, Level: string(ba.Spec.Level)})
|
||||
grants = append(grants, g)
|
||||
}
|
||||
return grants, len(principals), nil
|
||||
}
|
||||
|
||||
// grantFromAccess translates a BucketAccess spec into the ceph grant model,
|
||||
// carrying the fine-grained scoping (paths, actions, conditions, raw statements).
|
||||
func grantFromAccess(uid string, ba *v1alpha1.BucketAccess) ceph.Grant {
|
||||
g := ceph.Grant{
|
||||
UID: uid,
|
||||
Level: string(ba.Spec.Level),
|
||||
Paths: ba.Spec.Paths,
|
||||
Actions: ba.Spec.Actions,
|
||||
}
|
||||
if c := ba.Spec.Conditions; c != nil {
|
||||
g.Conditions = &ceph.GrantConditions{
|
||||
SourceIPs: c.SourceIPs,
|
||||
SecureTransportOnly: c.SecureTransportOnly,
|
||||
}
|
||||
}
|
||||
for _, s := range ba.Spec.RawStatements {
|
||||
g.Raw = append(g.Raw, ceph.RawStatement{
|
||||
Sid: s.Sid,
|
||||
Effect: s.Effect,
|
||||
Actions: s.Actions,
|
||||
Resources: s.Resources,
|
||||
Condition: s.Conditions,
|
||||
})
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// grantKey is a stable fingerprint of a grant used to collapse duplicate
|
||||
// BucketAccess objects that would render identical policy statements.
|
||||
func grantKey(g ceph.Grant) string {
|
||||
b, _ := json.Marshal(g)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (r *BucketReconciler) pending(ctx context.Context, b *v1alpha1.Bucket, reason, msg string) (ctrl.Result, error) {
|
||||
b.Status.Phase = "Pending"
|
||||
b.Status.ObservedGeneration = b.Generation
|
||||
|
||||
Reference in New Issue
Block a user