Skip to content

Commit

Permalink
Updated documentation
Browse files Browse the repository at this point in the history
Signed-off-by: Micah Hausler <mhausler@amazon.com>
  • Loading branch information
micahhausler committed Nov 12, 2024
1 parent f305d11 commit 365b700
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 31 deletions.
12 changes: 9 additions & 3 deletions docs/CedarIntroduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ forbid (

How does Cedar know if a principal is in a group?
When evaluating a request, Cedar has several inputs.
(You can play with some examples in the [Cedar Playground](https://www.cedarpolicy.com/en/playground)):
(You can play with some examples for Kubernetes in the [Cedar Playground](https://www.cedarpolicy.com/en/playground)):

* A JSON list of entity structures to be considered
* A principal identifier, which must be in the entities list
Expand Down Expand Up @@ -114,12 +114,12 @@ If an entity is a member of another entity type, the Entities list must say so.
```

Given the above input, the entity with the id `507B11AD-4DE0-44B1-AB7C-99C0C04854B1` and the name `alice` has a `parents` reference to the Group type named `viewers`, so in this case Alice is a member of the group `viewers`.
Cedar also supports membership on resource entities and verbs, but we don't use them in this project.
Cedar also supports membership on resource entities and verbs, which we'll get to later.

## Schema

When writing policy how do you know what is a valid attribute of a type so you can write policy against it?
Cedar policy supports a [schema] defining all valid entities (principals and resources), their attributes, actions, and which actions apply to which entities (principal and resources).
Cedar policy supports a [schema] defining all valid entities (principals and resources), their attributes, actions, which actions apply to which entities (principal and resources), and what the context structure for a given action is.
You can see the Cedar schema used for Kubernetes authorization this project in [human][authz_human_schema] and [json][authz_json_schema] format.

[schema]: https://docs.cedarpolicy.com/schema/schema.html
Expand Down Expand Up @@ -150,6 +150,12 @@ cedar validate -s ./cedarschema/k8s-full.cedarschema --schema-format cedar -p al
[cedar_go]: https://pkg.go.dev/github.com/cedar-policy/cedar-go
[cedar_cli]: https://crates.io/crates/cedar-policy-cli

The Makefile includes a target to validate all `.cedar` poilcy files in this repostiory.

```bash
make validate
```

To regenerate all schema files in both JSON and cedar formats (`k8s-full.cedarschema` and `k8s-full.cedarschema.json`), run:

```sh
Expand Down
104 changes: 86 additions & 18 deletions docs/CedarSchemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The referenced schemas are primarily created to document the entity shapes and a

## Authorizer cedarschema

For the full authorization schema, see [cedarschema/k8s-authorization.cedarschema](../cedarschema/k8s-authorization.cedarschema).
For the authorization schema, see [cedarschema/k8s-authorization.cedarschema](../cedarschema/k8s-authorization.cedarschema).

### Principals

Expand All @@ -30,10 +30,10 @@ This project supports the following Principal entities:
"extra"?: Set < ExtraAttribute >,
"name": __cedar::String
};
type Extra = {
"key": __cedar::String,
"values"?: Set < __cedar::String >
};
type Extra = {
"key": __cedar::String,
"values"?: Set < __cedar::String >
};
```
* `k8s::ServiceAccount`. When a user's name in a [SubjectAccessReview] starts with `system:serviceaccount:`, the authorizer sets the principal type to `k8s::ServiceAccount` with the following attributes.
```cedarschema
Expand Down Expand Up @@ -70,7 +70,7 @@ permit (
};
```

We do have a resource group for all readOnly actions. It encompasses `get`/`list`/`watch`, and is called `readOnly`, and only applies to `k8s::Resource` resources.
We do have an action group for all read-only actions. It encompasses `get`/`list`/`watch`, and is called `readOnly`, and only applies to `k8s::Resource` resources.

```cedar
permit (
Expand All @@ -85,15 +85,19 @@ permit (

### Resources

> Note: `"resource"`, `"k8s::Resource"`, and `"resource.resource`, why the redundancy?!
> **`"resource"`, `"k8s::Resource"`, and `"resource.resource`, why the redundancy?!**
>
> This is an unfortunate naming collision. Cedar policies always have a `resource` as part of the policy.
> We call Kubernetes typed objects `k8s::Resource` as opposed to the `k8s::NonResourceURL` type, because that's what Kubernetes calls them.
> And finally, Kubernetes authorization checks refer to they type of object as a `resource`, along with the object's `apiGroup`, `namespace`, `name`, etc.
We define two primary resource types for this authorizer:

* `NonResourceURL`: This is for non-resource requests made to the Kubernetes API server. Examples include `/healthz`, `/livez`, `/metrics`, and subpaths. (Hint: run `kubectl get --raw /` to see others) Paths can match a `*` on the suffix.
* `NonResourceURL`: This is for non-resource requests made to the Kubernetes API server.
Examples include `/healthz`, `/livez`, `/metrics`, and subpaths
(Hint: run `kubectl get --raw /` to see others).
A request's path is also used as the identifier in the entity list when evaluated for authorization.
Paths can match a `*` on the suffix.
```cedarschema
entity NonResourceURL = {
"path": __cedar::String
Expand All @@ -117,7 +121,8 @@ We define two primary resource types for this authorizer:
resource == k8s::NonResourceURL::"/version"
);
```
* `Resource`: This is for resource requests made to the Kubernetes API server. Entity IDs on resources are the constructed URL path being made for the request.
* `Resource`: This is for resource requests made to the Kubernetes API server.
Entity IDs on resources are the constructed URL path being made for the request.
```cedarschema
entity Resource = {
"apiGroup": __cedar::String,
Expand Down Expand Up @@ -166,7 +171,7 @@ We define two primary resource types for this authorizer:
};
```

`Resource` has a `fieldSelector` and `labelSelector` types. These were added in Kubernetes 1.31 behind the [`AuthorizeWithSelectors`][AuthorizeWithSelectors] feature gate so authorizers can enforce that a watch or list request has a field or label selector:
`Resource` has a `fieldSelector` and `labelSelector` types. These were [added in Kubernetes 1.31][AuthorizeWithSelectors] behind the `AuthorizeWithSelectors` feature gate so authorizers can enforce that a watch or list request has a field or label selector:

[AuthorizeWithSelectors]: https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/

Expand All @@ -184,10 +189,10 @@ type LabelRequirement = {
};
```

This can be used to enforce attribute-based access policies, such as a user can only get/list/watch resources where the label `owner` equals the user's name
Selectors can be used to enforce attribute-based access policies, such as enforcing that a user can only get/list/watch resources where the label `owner` equals the user's name
```cedar
permit (
principal is User,
principal is k8s::User,
action in [k8s::Action::"list", k8s::Action::"watch"],
resource is k8s::Resource
) when {
Expand Down Expand Up @@ -220,7 +225,7 @@ other-example-secret Opaque 1 2d20h owner=prod-user

#### Impersonated resources

To make an impersonated request as another user, Kubernetes sends multiple authorization requests to an authorizer: one for each attribute being impersonated: The user's name, the UID (if set), the groups (if set), and the userInfo extra key/value map (entity tags are [not yet supported in cedar-go](https://github.com/cedar-policy/cedar-go/issues/47)). To support this, we define a few types:
To make an impersonated request as another user, Kubernetes sends multiple authorization requests to an authorizer: one for each attribute being impersonated: The user's name, the UID (if set), the groups (if set), and the userInfo extra key/value map. To support this, we define a few types:

* `Group`. This structure is the same from the principal type. This only functions if the user can also impersonate the requested username.:
```cedar
Expand Down Expand Up @@ -255,7 +260,10 @@ To make an impersonated request as another user, Kubernetes sends multiple autho
```
* `Extra`: To allow impersonating a principal's key/values extra info, the policy's resource type must be `Extra`. This only functions if the user can also impersonate the requested username.
```cedarschema
entity PrincipalUID;
entity Extra = {
"key": __cedar::String,
"values"?: Set < __cedar::String >
};
```
Examples:
```cedar
Expand Down Expand Up @@ -344,10 +352,10 @@ forbid (
);
```

All admission actions currently apply to any Kubernetes type that have a [`metav1.ObjectMeta`][objmeta] or [`corev1.ListMeta`][listmeta].
Most admission actions currently apply to any Kubernetes type that have a [`metav1.ObjectMeta`][objmeta].

[objmeta]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#ObjectMeta
[listmeta]: https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#ListMeta


### Resources

Expand All @@ -358,7 +366,8 @@ The resource entity structure matches that of the Kubernetes API structure, with
// Forbid pods with hostNetwork in namespaces other than kube-system
forbid (
principal,
action in [k8s::admission::Action::"create", k8s::admission::Action::"update"],
action in [
k8s::admission::Action::"create", k8s::admission::Action::"update"],
resource is core::v1::Pod
) when {
resource has spec &&
Expand All @@ -371,7 +380,7 @@ forbid (
};
```

Until [cedar-go supports entity maps][go-entity-maps], we've manually added `KeyValue` and `KeyValues` types into the `meta::v1` namespace to support key/value labels.
Until [cedar-go supports entity maps][go-entity-maps], we've manually added `KeyValue` and `KeyValueStringSlice` types into the `meta::v1` namespace to support key/value labels.
Any Kubernetes types that consist of `map[string]string{}` or `map[string][]string{}` are converted to a Set of KeyValue or KeyValueStringSlice.
```cedarschema
namespace meta::v1 {
Expand All @@ -397,3 +406,62 @@ namespace meta::v1 {
```

[go-entity-maps]: https://github.com/cedar-policy/cedar-go/issues/47

The Kubernetes `CONNECT` admission action only applies to a small set of structures that don't appear in the Kubernetes OpenAPI Schema, so we inject them manually:
```cedarschema
namespace core::v1 {
// other types and entities
entity NodeProxyOptions = {
"apiVersion": __cedar::String,
"kind": __cedar::String,
"path": __cedar::String
};
entity PodAttachOptions = {
"apiVersion": __cedar::String,
"command": Set < __cedar::String >,
"container": __cedar::String,
"kind": __cedar::String,
"stderr": __cedar::Bool,
"stdin": __cedar::Bool,
"stdout": __cedar::Bool,
"tty": __cedar::Bool
};
entity PodExecOptions = {
"apiVersion": __cedar::String,
"command": Set < __cedar::String >,
"container": __cedar::String,
"kind": __cedar::String,
"stderr": __cedar::Bool,
"stdin": __cedar::Bool,
"stdout": __cedar::Bool,
"tty": __cedar::Bool
};
entity PodPortForwardOptions = {
"apiVersion": __cedar::String,
"kind": __cedar::String,
"ports"?: Set < __cedar::String >
};
entity PodProxyOptions = {
"apiVersion": __cedar::String,
"kind": __cedar::String,
"path": __cedar::String
};
entity ServiceProxyOptions = {
"apiVersion": __cedar::String,
"kind": __cedar::String,
"path": __cedar::String
};
}
```

Policy can be used to forbid proxying to those types:
```cedar
// deny policy on exec unless command is `whoami`
forbid (
principal,
action == k8s::admission::Action::"connect",
resource is core::v1::PodExecOptions
) unless {
resource.command = ["whoami"]
};
```
2 changes: 2 additions & 0 deletions docs/ConvertRBAC.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Convert RBAC policies

There's an RBAC converter that works on ClusterRoleBindings or RoleBindings.
This converter is intended to help you transition any RBAC policies you want to add conditions to into Cedar.
Any existing RBAC policies that aren't denied by a Cedar policy will still work.

If not done already, clone this repository to your local environment or IDE.

Expand Down
26 changes: 21 additions & 5 deletions docs/Demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,19 @@ We have a couple of policies already written on the [`demo/authorization-policy.
@description("test-user can get/list/watch pods")
permit (
principal is k8s::User,
action in [k8s::Action::"get", k8s::Action::"list", k8s::Action::"watch"],
action in [
k8s::Action::"get",
k8s::Action::"list",
k8s::Action::"watch"],
resource is k8s::Resource
) when {
principal.name == "test-user" &&
// "" is the core API group in Kubernetes
resource.apiGroup == "" &&
resource.resource == "pods"
} unless {
// we don't want to allow a GET on a portforward subresource
resource has subresource
};
@description("forbid test-user to get/list/watch nodes")
Expand Down Expand Up @@ -93,7 +99,10 @@ Given that the test-user is in the group `viewers`, lets leverage that group by
// viewer group members can get/list/watch any Resource other than secrets in the default namespace
permit (
principal in k8s::Group::"viewers",
action in [ k8s::Action::"get", k8s::Action::"list", k8s::Action::"watch"],
action in [
k8s::Action::"get",
k8s::Action::"list",
k8s::Action::"watch"],
resource is k8s::Resource
) unless {
resource.resource == "secrets" &&
Expand Down Expand Up @@ -274,7 +283,9 @@ permit (
// Admission policy preventing test-user from creating/updating configmaps with name starting with "prod"
forbid (
principal is k8s::User,
action in [k8s::admission::Action::"create", k8s::admission::Action::"update"],
action in [
k8s::admission::Action::"create",
k8s::admission::Action::"update"],
resource is core::v1::ConfigMap
) when {
principal.name == "test-user" &&
Expand Down Expand Up @@ -339,7 +350,9 @@ permit (
// without label selector owner={principal.name}
forbid (
principal is k8s::User in k8s::Group::"requires-labels",
action in [k8s::Action::"list", k8s::Action::"watch"],
action in [
k8s::Action::"list",
k8s::Action::"watch"],
resource is k8s::Resource
) unless {
resource has labelSelector &&
Expand All @@ -353,7 +366,10 @@ forbid (
// admission policy to forbid resource creation without an owner key
forbid (
principal is k8s::User in k8s::Group::"requires-labels",
action in [k8s::admission::Action::"create", k8s::admission::Action::"update", k8s::admission::Action::"delete"],
action in [
k8s::admission::Action::"create",
k8s::admission::Action::"update",
k8s::admission::Action::"delete"],
resource
) unless {
resource has metadata &&
Expand Down
4 changes: 2 additions & 2 deletions docs/Limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ RBAC would require the following policy to permit a port-forward request.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: deployment-manager
name: service-port-forwarder
rules:
- apiGroups:
- ""
Expand Down Expand Up @@ -109,7 +109,7 @@ namespace k8s {
## Expressiveness limitations

A core tenet of Cedar is to be analyzable, meaning that the language can verify that a policy is valid and will not error.
A general `map`/`filter` function on dynamic inputs [is not analyzible][rfc21], and not a candidate for the project.
A general `map`/`filter` function on dynamic inputs and ordered lists [are not analyzible][rfc21], and not a candidate for Cedar.
This prevents specifically checking subfields over sets of structures, which is a common Kubernetes policy management requirement.
Cedar is powered by [automated reasoning], including an [SMT solver], which does not implement loops or map functions.
Rather than viewing Cedar as a replacement for admission restrictions tools like [Open Policy Agent/Gatekeeper][gatekeeper] or [Kyverno][kyverno], it is best seen as an additional tool for access control enforcement.
Expand Down
3 changes: 0 additions & 3 deletions internal/server/authorizer/entitiy_builders.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,5 @@ func ResourceToCedarEntity(attributes authorizer.Attributes) cedartypes.Entity {
ID: cedartypes.String(entities.ResourceRequestToPath(attributes)),
},
Attributes: cedartypes.NewRecord(respAttributes),
// TODO: Parent of Namespace Entity for namespaced resources?
// maybe the best argument for a namespaced resource
// or everything has a namespace parent of "all"?
}
}
4 changes: 4 additions & 0 deletions manifests/cedar-authorization-webhook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ spec:
periodSeconds: 1
timeoutSeconds: 15
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 250m
memory: 128Mi
startupProbe:
failureThreshold: 24
httpGet:
Expand Down

0 comments on commit 365b700

Please sign in to comment.