Skip to content

Instantly share code, notes, and snippets.

@mikedanese
Last active April 24, 2019 04:05
Show Gist options
  • Save mikedanese/03361c9bb634008bd6a0a0c31db6d555 to your computer and use it in GitHub Desktop.
Save mikedanese/03361c9bb634008bd6a0a0c31db6d555 to your computer and use it in GitHub Desktop.
// pseudo-code sketch of condition support in Kubernetes authorization
package conditions
// authorization.k8s.io changes
type SubjectAccessReviewSpec struct {
// ...
ExtraAttributes map[string]string
}
type SubjectAccessReviewStatus struct {
// ...
Condition *Condition
}
type Condition struct {
Expression string
}
// new attribute calculator API
// Environment holds bindings to attributes required to evaluate a conditional
// expression. It is expected to be created per invocation of a condition
// program.
type Environment struct {
Bindings map[string]interface{}
}
type AttributeCalculator interface {
// Bind binds calcluated attributes to a conditional expression.
//
// TODO: explain how lazy evaluation works.
Bind(context.Context, admission.Attributes, Environment) error
}
// new condition evaluater
type condEvaler struct {
calculators []AttributeCalculator
}
func (a *condEvaler) Prepare(ctx context.Context, attrs admission.Attributes, cond Condition) (Condition, error) {
// create a new environment
env := NewEnvironment()
// bind derived attributes to the environment by querying all registered
// attribute calculators
for _, c := range ca.calculators {
c.Bind(ctx, attrs, env)
}
// partially evalulate the condition
EvalPartial(&cond, env)
// return a pruned/simplified equivalent condition
return cond, nil
}
// Example usage
//
// There are two obvious places where condition evaluation occurs:
// * somewhere near the authorization filter. This is a complete evaluation of
// the condition that gate's the request's admission.
// * in the SubjectAccessReview registry. This is a partial evaluation of the
// condition.
//
// This example covers the first.
type filter struct {
authz authorizer.Authorizer
ce *condEvaler
next Filter
}
func (f *filter) Do(ctx context.Context, req http.Request) (http.Response, error) {
// first query the authorizers
resp := f.authz.Authorize(ctx, authorizer.MakeAttributes(req))
// if the authorizers do not return a conditional yes, return immediately
if !resp.Denied || resp.Condition == nil {
return resp, nil
}
// partially evaluate the condition
resp.Condition = ca.Prepare(ctx, admission.MakeAttributes(req), resp.Condition)
// attempt to fully evaluate the condition. if successful, return an
// unconditional yes.
if err := EvalFull(resp.Condition); err {
// else return the simplified condition
return fmt.Errorf("authz failed: %v", err)
}
return f.next.Do(ctx, req)
}
// Example RBAC binding.
//
// This RBAC role grants cluster admin to
//
// apiVersion: rbac.authorization.k8s.io/v1
// kind: ClusterRoleBinding
// metadata:
// name: node-scoped
// roleRef:
// apiGroup: rbac.authorization.k8s.io
// kind: ClusterRole
// name: cluster-admin
// subjects:
// - apiGroup: rbac.authorization.k8s.io
// kind: ServiceAccount
// namespace: kube-system
// name: thingy
// condition:
// # isRechable doesn't need any arguments here because the attribute
// # calcluator binds a function to isReachable that captures required state
// # from request attributes.
// expression: "nodeGraph.isReachable()"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment