Skip to content

Commit bcb55aa

Browse files
committed
control api architecture
1 parent fd2600b commit bcb55aa

File tree

6 files changed

+321
-0
lines changed

6 files changed

+321
-0
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
= {controlapi}: Organization
2+
3+
TIP: This resource implements the xref:references/functional-requirements/portal.adoc#_feature_manage_organizations["Manage Organizations"] features.
4+
5+
List operations on Kubernetes resources can not be filtered by RBAC, it's a binary operation:
6+
Resources can either be listed or not, there is no way to give access only to a sub-set.
7+
To circumvent this limitation, `Organization` is a virtual resource.
8+
9+
The `Organization` resource represents a filtered and formatted list of standard Kubernetes `Namespace` resources which have specific labels and annotations.
10+
11+
It is assumed that the `Organization` resource is used for all operations, the represented `Namespace` must not be directly manipulated.
12+
13+
== Object
14+
15+
.Virtual resource
16+
[source,yaml]
17+
----
18+
apiVersion: appuio.io/v1
19+
kind: Organization
20+
metadata:
21+
name: acme-corp <1>
22+
annotations:
23+
organization.appuio.io/namespace: org-acme-corp <2>
24+
spec:
25+
displayName: Acme Corp. <3>
26+
----
27+
Field mapping from the represented `Namespace` resource:
28+
29+
<1> `metadata.labels[appuio.io/metadata.name]`
30+
<2> `metadata.name`
31+
<3> `metadata.annotations[organization.appuio.io/display-name]`
32+
33+
An https://book.kubebuilder.io/reference/generating-crd.html#additional-printer-columns[additional printer column] can help to identify the associated namespace resource name.
34+
This is useful when working with organization-scoped objects which are available in the organization's namespace.
35+
36+
.Original resource
37+
[source,yaml]
38+
----
39+
apiVersion: v1
40+
kind: Namespace
41+
metadata:
42+
name: org-acme-corp <1>
43+
labels:
44+
appuio.io/resource.type: organization <2>
45+
appuio.io/metadata.name: acme-corp <3>
46+
annotations:
47+
organization.appuio.io/display-name: Acme Corp. <4>
48+
----
49+
<1> Resource name, prefixed with `org-` to circumvent possible name collision
50+
<2> Identify resource type, used by the API server to filter for namespaces representing organizations
51+
<3> `metadata.name` of the virtual `Organization` object
52+
<4> Reflected in the `Organization` object as `spec.displayName`
53+
54+
== Labels and Annotations
55+
56+
[cols="2,1,1,3",options="header"]
57+
|===
58+
|Name
59+
|Type
60+
|Resource
61+
|Description
62+
63+
|`appuio.io/resource.type`
64+
|label
65+
|`v1/Namespace`
66+
|Identifies the resource type in the scope of the {controlapi}
67+
68+
|`appuio.io/metadata.name`
69+
|label
70+
|`v1/Namespace`
71+
|`metadata.name` of the virtual resource
72+
73+
|`organization.appuio.io/display-name`
74+
|annotation
75+
|`appuio.io/v1/Organization`
76+
|Display name of the organization
77+
78+
|===
79+
80+
== Resource filter
81+
82+
The virtual resource is a filtered view of `Namespaces`.
83+
The filter uses the following heuristic:
84+
85+
* API version: `v1`
86+
* Kind: `Namespace`
87+
* Label: `appuio.io/resource-type=organization`
88+
* Subject is bound to one of the defined `ClusterRole` resources.
89+
90+
== Cluster roles
91+
92+
These are `ClusterRole` resources which are bound to a subject by a namespaced `RoleBinding`:
93+
94+
`org-view`:: View (read only) access to an organization
95+
`org-admin`:: Admin (read / write) access to an organization
96+
97+
By default, creating organizations can be done by all authenticated users.
98+
99+
== Definition of Organization Membership
100+
101+
A subject is seen as member of an Organization when it's referenced in at least one `RoleBinding`.
102+
This can also be an indirect membership via a Team membership.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
= {controlapi}: Team
2+
3+
TIP: This resource implements the xref:references/functional-requirements/portal.adoc#_feature_manage_teams["Manage Teams"] features.
4+
5+
== Object
6+
7+
.CRD based
8+
[source,yaml]
9+
----
10+
apiVersion: appuio.io/v1
11+
kind: Team
12+
metadata:
13+
name: myteam1
14+
namespace: org-acme-corp <1>
15+
spec:
16+
displayName: My Super Team 1
17+
userRefs: <2>
18+
- id: bec0d928-2ae2-4cec-94a0-5f72f12b8b39
19+
- userName: peter.muster
20+
status:
21+
resolvedNames: <3>
22+
- id: bec0d928-2ae2-4cec-94a0-5f72f12b8b39
23+
userName: kate.demo
24+
- id: 508a9160-977c-4c57-963f-c7b511c4ecc5
25+
userName: peter.muster
26+
----
27+
<1> The organizations namespace
28+
<2> References to one or more xref:references/architecture/control-api-user.adoc[`User`] objects. +
29+
Only one of the two parameters are allowed:
30+
31+
* `id` must match `metadata.name` of an existing `User` resource
32+
* `name` must match `spec.username` from an existing `User` resource
33+
<3> This resolved by the xref:explanation/system/details-adapters.adoc[adapter]
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
= {controlapi}: User
2+
3+
TIP: This resource implements the xref:references/functional-requirements/portal.adoc#_feature_personal_settings_per_user["Personal Settings per User"] feature.
4+
5+
The `User` resource is a virtual resource for the same reasons like the xref:references/architecture/control-api-org.adoc[`Organization`] resource.
6+
7+
== Object
8+
9+
.Virtual resource
10+
[source,yaml]
11+
----
12+
apiVersion: appuio.io/v1
13+
kind: User
14+
metadata:
15+
name: bec0d928-2ae2-4cec-94a0-5f72f12b8b39 <1>
16+
spec:
17+
displayName: Kate Demo <2>
18+
username: kate.demo <3>
19+
email: [email protected] <4>
20+
defaultOrganizationRef: acme-corp <5>
21+
----
22+
Field mapping from the represented `Namespace` resource and from the OIDC token:
23+
24+
<1> Namespace: `metadata.name` +
25+
Token: `sub`
26+
<2> Namespace: `metadata.annotations[user.appuio.io/display-name]` +
27+
Token: `name`
28+
<3> Namespace: `metadata.annotations[user.appuio.io/username]` +
29+
Token: `preferred_username`
30+
<4> Namespace: `metadata.annotations[user.appuio.io/email]` +
31+
Token: `email`
32+
<5> Namespace: `metadata.annotations[user.appuio.io/default-organization]`
33+
34+
.Original resource
35+
[source,yaml]
36+
----
37+
apiVersion: v1
38+
kind: Namespace
39+
metadata:
40+
name: bec0d928-2ae2-4cec-94a0-5f72f12b8b39 <1>
41+
labels:
42+
appuio.io/resource.type: user <2>
43+
annotations:
44+
user.appuio.io/display-name: Kate Demo <3>
45+
user.appuio.io/username: kate.demo <4>
46+
user.appuio.io/email: [email protected] <5>
47+
user.appuio.io/default-organization: acme-corp <6>
48+
----
49+
<1> Field `sub` from the OIDC JWT token, representing the unique user ID in the {idp}
50+
Reflected in the `User` object as `metadata.labels[appuio.io/user.id]`
51+
<2> Identify resource type, used by the API server to filter for namespaces representing users
52+
<3> Reflected in the `User` object as `spec.displayName`
53+
<4> Reflected in the `User` object as `spec.username`
54+
<5> Reflected in the `User` object as `spec.email`
55+
<6> Reflected in the `User` object as `spec.defaultOrganizationRef`
56+
57+
== Resource filter
58+
59+
The virtual resource is a filtered view of `Namespaces`.
60+
The filter uses the following heuristic:
61+
62+
* API version: `v1`
63+
* Kind: `Namespace`
64+
* Label: `appuio.io/resource-type=user`
65+
66+
== Access control
67+
68+
There is a 1:1 correlation between the subjects `sub` field from the OIDC JWT token and the `metadata.name` field of the `User` resource.
69+
A subject only has access to its own `User` object.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
= {controlapi}: Zone
2+
3+
TIP: This resource implements the xref:references/functional-requirements/portal.adoc#_feature_zones["Manage Zones"] features.
4+
5+
== Object
6+
7+
.CRD based
8+
[source,yaml]
9+
----
10+
apiVersion: appuio.io/v1
11+
kind: Zone
12+
metadata:
13+
name: cloudscale-lpg-0
14+
spec:
15+
displayName: cloudscale.ch LPG 0
16+
features:
17+
openshift-version: "4.8"
18+
kubernetes-version: "1.21"
19+
sdn: OVN-Kubernetes
20+
urls:
21+
console: https://console.cloudscale-lpg-0.appuio.cloud/
22+
kubernetes-api: https://api.cloudscale-lpg-0.appuio.cloud:6443/
23+
registry: https://registry.cloudscale-lpg-0.appuio.cloud
24+
logging: https://logging.cloudscale-lpg-0.appuio.cloud/
25+
cname: cname.cloudscale-lpg-0.appuio.cloud
26+
default-app-domain: apps.cloudscale-lpg-0.appuio.cloud
27+
gateway-ips:
28+
- 185.98.123.122
29+
cloud-provider:
30+
name: cloudscale.ch
31+
zones:
32+
- lpg1
33+
region: Lupfig (AG)
34+
----
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
= {controlapi} Architecture
2+
3+
This page describes the xref:references/glossary.adoc#_control_api[{controlapi}].
4+
It adheres to the decision taken in xref:explanation/decisions/control-api.adoc[{controlapi}] and it follows the features asked in xref:references/functional-requirements/portal.adoc[Functional Requirements Targeting the Portal].
5+
6+
== General Principles
7+
8+
Kubernetes API::
9+
The Control API is built upon the Kubernetes API and adheres to it's https://kubernetes.io/docs/reference/kubernetes-api/[design principles].
10+
11+
API object field: `.status`::
12+
The `status` field (also called https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#status-subresource["Status subresource"]) represents the current observed state and external information.
13+
It is read-only to the end-user.
14+
15+
API object field: `.spec`::
16+
The `spec` field contains the desired state for reconciliation.
17+
18+
== Virtual vs. CRD based resources
19+
20+
Some resources are not persisted to etcd and are only available _virtually_, others are persisted to etcd and are defined and represented via `CustomResourceDefinition` (CRDs).
21+
22+
Virtual resources are accessible via an https://kubernetes.io/docs/tasks/extend-kubernetes/setup-extension-api-server/[API Server Extension].
23+
These resources are similar to views in a relational database.
24+
The benefit of providing these resources instead of only using CRDs is that we can calculate access permissions dynamically for every request.
25+
The same concept is also used by OpenShift with its `Project` resource which represents RBAC filtered `Namespaces` (see https://github.com/openshift/kube-projects[kube-projects]).
26+
And we can also find it in https://github.com/loft-sh/kiosk[Kiosk] for example.
27+
28+
== Authentication and Authorization
29+
30+
Authentication against the API server is done by the {idp}.
31+
It's always the same subject (user) which is being used throughout the whole {product} ecosystem.
32+
33+
For authorization, standard https://kubernetes.io/docs/reference/access-authn-authz/rbac/[Kubernetes RBAC] is being used.
34+
Kyverno policies can be used to implement enhanced policies, for example the number of resources of a specified kind a user is allowed to create.
35+
36+
There are several layers of authorization:
37+
38+
* Kubernetes RBAC
39+
* Kyverno policies
40+
* Virtual resources with filtering
41+
42+
== Resource Scopes
43+
44+
Resources on the Kubernetes API server can either be https://kubernetes.io/docs/reference/using-api/api-concepts/#standard-api-terminology[cluster scoped or namespace scoped].
45+
46+
Each {controlapi} instance is represented by one Kubernetes API server instance.
47+
This allows us to leverage the scoping concept of the Kubernetes API server to reflect the scopes in the {product} domain.
48+
Also, by doing it that way, standard Kubernetes RBAC rules can be used for permission handling on an organization level.
49+
50+
{global} resources are available on the Kubernetes global scope (no namespace) whereas organization level resources are namespace scoped.
51+
52+
Global:: Kubernetes cluster global resource.
53+
Organization:: Kubernetes namespaced resource.
54+
55+
== Resources
56+
57+
[cols="1,1,2",options="header"]
58+
|===
59+
|Name
60+
|Scope
61+
|Type
62+
63+
|xref:references/architecture/control-api-org.adoc[Organization]
64+
|Global
65+
|Virtual (represents filtered `Namespace` resources)
66+
67+
|xref:references/architecture/control-api-user.adoc[User]
68+
|Global
69+
|CRD
70+
71+
|xref:references/architecture/control-api-team.adoc[Team]
72+
|Organization
73+
|CRD
74+
75+
|xref:references/architecture/control-api-zone.adoc[Zone]
76+
|Global
77+
|CRD
78+
|===

docs/modules/ROOT/partials/nav-reference.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
** xref:appuio-cloud:ROOT:references/architecture/namespace-ownership.adoc[Namespace ownership]
1212
** xref:appuio-cloud:ROOT:references/architecture/metering.adoc[Metering of resource usage]
1313
** xref:appuio-cloud:ROOT:references/architecture/metrics-of-interest.adoc[Metrics of interest]
14+
** xref:appuio-cloud:ROOT:references/architecture/control-api.adoc[Control API]
15+
*** xref:appuio-cloud:ROOT:references/architecture/control-api-org.adoc[Organization]
16+
*** xref:appuio-cloud:ROOT:references/architecture/control-api-user.adoc[User]
17+
*** xref:appuio-cloud:ROOT:references/architecture/control-api-team.adoc[Team]
18+
*** xref:appuio-cloud:ROOT:references/architecture/control-api-zone.adoc[Zone]
1419

1520
* Functional Requirements
1621

0 commit comments

Comments
 (0)