Skip to content

Commit 910f054

Browse files
committed
control api architecture
1 parent fd2600b commit 910f054

File tree

6 files changed

+340
-0
lines changed

6 files changed

+340
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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+
== RBAC and 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+
== Organization Membership
100+
101+
All members of an organization are configured in an `OrganizationMembers` resource.
102+
103+
.CRD based
104+
[source,yaml]
105+
----
106+
apiVersion: appuio.io/v1
107+
kind: OrganizationMembers
108+
metadata:
109+
name: acme-corp-members
110+
namespace: org-acme-corp
111+
spec:
112+
userRefs: <1>
113+
- id: bec0d928-2ae2-4cec-94a0-5f72f12b8b39
114+
- username: peter.muster
115+
status:
116+
resolvedUserRefs: <2>
117+
- id: bec0d928-2ae2-4cec-94a0-5f72f12b8b39
118+
username: kate.demo
119+
- id: 508a9160-977c-4c57-963f-c7b511c4ecc5
120+
username: peter.muster
121+
----
122+
<1> References to one or more xref:references/architecture/control-api-user.adoc[`User`] resource. +
123+
Only one of the two parameters are allowed:
124+
125+
* `id` must match `metadata.name` of an existing `User` resource
126+
* `username` must match `spec.username` from an existing `User` resource
127+
<2> This is resolved by the xref:explanation/system/details-adapters.adoc[adapter]
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+
resolvedUserRefs: <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`] resource. +
29+
Only one of the two parameters are allowed:
30+
31+
* `id` must match `metadata.name` of an existing `User` resource
32+
* `username` must match `spec.username` from an existing `User` resource
33+
<3> This resolved by the xref:explanation/system/details-adapters.adoc[adapter]
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
== Object
6+
7+
.CRD based
8+
[source,yaml]
9+
----
10+
apiVersion: appuio.io/v1
11+
kind: User
12+
metadata:
13+
name: bec0d928-2ae2-4cec-94a0-5f72f12b8b39 <1>
14+
spec:
15+
displayName: Kate Demo
16+
17+
defaultOrganizationRef: acme-corp
18+
status: <2>
19+
displayName: Kate Demo
20+
username: kate.demo
21+
22+
defaultOrganizationRef: acme-corp
23+
----
24+
<1> User ID in Keycloak
25+
<2> Reflects actual configuration from adapter and exposes read-only fields
26+
27+
== Access control
28+
29+
As this is a cluster-scoped resource, access control has to be defined in the cluster scope.
30+
For each `User` object, a `ClusterRole` and `ClusterRoleBinding` is generated in the background, which grants edit rights to the subject (owner) which relates to the `User`.
31+
32+
[source,yaml]
33+
----
34+
kind: ClusterRole
35+
metadata:
36+
name: bec0d928-2ae2-4cec-94a0-5f72f12b8b39-owner
37+
rules:
38+
- apiGroups: ["appuio.io"]
39+
resources: ["users"]
40+
resourceNames: ["bec0d928-2ae2-4cec-94a0-5f72f12b8b39"]
41+
verbs: ["get", "update", "patch", "delete"]
42+
---
43+
apiVersion: rbac.authorization.k8s.io/v1
44+
kind: ClusterRoleBinding
45+
metadata:
46+
name: bec0d928-2ae2-4cec-94a0-5f72f12b8b39-owner
47+
subjects:
48+
- kind: User
49+
name: appuio#bec0d928-2ae2-4cec-94a0-5f72f12b8b39 <1>
50+
apiGroup: rbac.authorization.k8s.io
51+
roleRef:
52+
kind: ClusterRole
53+
name: bec0d928-2ae2-4cec-94a0-5f72f12b8b39-owner
54+
apiGroup: rbac.authorization.k8s.io
55+
----
56+
<1> This depends on the API server configuration: +
57+
`oidc-username-claim=sub` and `oidc-username-prefix=appuio#`
58+
59+
By default, only the subject which is the owner of the `User` object gets edit rights.
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+
data:
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: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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-org.adoc#_organization_membership[OrganizationMembers]
68+
|Organization
69+
|CRD
70+
71+
|xref:references/architecture/control-api-user.adoc[User]
72+
|Global
73+
|CRD
74+
75+
|xref:references/architecture/control-api-team.adoc[Team]
76+
|Organization
77+
|CRD
78+
79+
|xref:references/architecture/control-api-zone.adoc[Zone]
80+
|Global
81+
|CRD
82+
|===

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)