mirror of
https://github.com/hanzoai/k8s.git
synced 2026-10-02 15:40:05 +00:00
The tree was staged but never committed — this is the repo's first commit, so it carries both the existing content and the zip v1.18.8 -> v1.20.0 migration. Migration: Prepare() is replaced by Build() error, and both test harnesses assert the verdict rather than discarding it. main.go:59's Use call needed no change — the three middleware constructors already return zip.Handler, which satisfies the closed Component interface. The committed projections (k8s.plugin.json, openapi.json, ops/zipdoc_gen.go) are regenerated: v1.20.0 splits an op's prose into summary (first line) + description and zipdoc now emits per-field docs, so all three artifacts move with the bump. Co-authored-by: Hanzo Dev <[email protected]>
58 lines
1.9 KiB
Go
58 lines
1.9 KiB
Go
package ops
|
|
|
|
import (
|
|
"context"
|
|
|
|
authv1 "k8s.io/api/authorization/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"github.com/hanzoai/k8s/plane"
|
|
)
|
|
|
|
// can asks a cluster whether this app's own credential may perform one act.
|
|
//
|
|
// It is a SelfSubjectAccessReview, which means the answer comes from the cluster's
|
|
// RBAC rather than from a grant this app asserts about itself. That distinction is the
|
|
// reason the op exists: the union ClusterRole this deployment needs is granted OUT of
|
|
// tree, so a drifted or half-applied grant is a real and undetectable condition — and
|
|
// asking beats asserting, because a refusal a caller can read is a feature it can
|
|
// degrade gracefully on instead of a 403 arriving in the middle of a mutation.
|
|
//
|
|
// Example: {"cluster":"hanzo-k8s","verb":"get","resource":"resourcequotas","namespace":"acme-prod"}
|
|
// Response: {"allowed":true}
|
|
func (o Ops) can(ctx context.Context, in *plane.CanIn) (*plane.Can, error) {
|
|
if err := need("verb", in.Verb); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := need("resource", in.Resource); err != nil {
|
|
return nil, err
|
|
}
|
|
b, err := o.reach(ctx, in.Cluster)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// A namespace, when given, is bounded like any other: asking about a namespace a
|
|
// registration may not address would let a tenant probe another tenant's RBAC.
|
|
ns := ""
|
|
if in.Namespace != "" {
|
|
ns, err = b.One(in.Namespace)
|
|
if err != nil {
|
|
return nil, fail(err)
|
|
}
|
|
} else if err := b.WholeCluster(); err != nil {
|
|
return nil, fail(err)
|
|
}
|
|
review := &authv1.SelfSubjectAccessReview{
|
|
Spec: authv1.SelfSubjectAccessReviewSpec{
|
|
ResourceAttributes: &authv1.ResourceAttributes{
|
|
Verb: in.Verb, Group: in.Group, Resource: in.Resource, Namespace: ns,
|
|
},
|
|
},
|
|
}
|
|
got, err := b.Typed.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, review, metav1.CreateOptions{})
|
|
if err != nil {
|
|
return nil, fail(err)
|
|
}
|
|
return &plane.Can{Allowed: got.Status.Allowed, Reason: got.Status.Reason}, nil
|
|
}
|