Skip to content

Commit 36cf376

Browse files
lexfreilllamnyp
andcommitted
docs(sdn): document the SecurityGroup aggregated-API design
Describe the implemented model: SecurityGroup as a synchronous 1:1 projection of a marker-labelled CiliumNetworkPolicy served by the aggregated API server, the marker invariant the storage enforces on every write, the RBAC boundary it provides, the in-tree mirror that avoids the Cilium module dependency, and the deferred attachment / cluster-scope / validation extensions. Assisted-By: Claude <[email protected]> Signed-off-by: Aleksei Sviridkin <[email protected]> Co-authored-by: Timofei Larkin <[email protected]>
1 parent 184f887 commit 36cf376

1 file changed

Lines changed: 119 additions & 0 deletions

File tree

‎pkg/apis/sdn/DESIGN.md‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Cozystack Security Groups — Design
2+
3+
**Status:** Implemented (v1alpha1, aggregated-API projection)
4+
5+
## 1. Summary
6+
7+
A **SecurityGroup** is a namespace-scoped, tenant-facing firewall object in the `sdn.cozystack.io/v1alpha1` API group. It is served by the Cozystack aggregated API server (`cozystack-api`) as a **1:1 projection of a single CiliumNetworkPolicy** in the same namespace. Tenants manage SecurityGroups; the API server translates each one, synchronously, into a CiliumNetworkPolicy on write and back on read. There is no separate controller and no extra reconcile loop.
8+
9+
The product value is the **RBAC boundary plus a Cozystack-native surface**: tenants get full self-service control over their own network policy without being granted any access to the `cilium.io` API group, and without the platform exposing Cilium's full, version-coupled CRD surface to them.
10+
11+
## 2. Goals / Non-Goals
12+
13+
**Goals**
14+
15+
- Let tenants declare allow-list ingress/egress for their own pods, scoped to their namespace.
16+
- Keep policy count equal to the number of SecurityGroups (1 SecurityGroup ↔ 1 CiliumNetworkPolicy).
17+
- Require no direct tenant access to `cilium.io` resources.
18+
- Avoid disrupting clusters or namespaces that do not create SecurityGroups.
19+
20+
**Non-Goals**
21+
22+
- We do not redesign tenant isolation; existing platform isolation policies remain and are untouched (they carry no SecurityGroup marker label, so they are invisible to this API).
23+
- We do not introduce an AWS-style attach/detach abstraction, reusable CIDR/FQDN groups, or per-attachment policy logic. Those are future work (§8).
24+
25+
## 3. Architecture
26+
27+
1. A tenant creates a **SecurityGroup** (`sdn.cozystack.io/v1alpha1`) with an `endpointSelector` and `ingress`/`egress` rules.
28+
2. The `cozystack-api` REST storage translates it into a **CiliumNetworkPolicy** of the same name in the same namespace, carrying the marker label `sdn.cozystack.io/securitygroup: "true"`.
29+
3. Reads (`get`/`list`/`watch`) project marked CiliumNetworkPolicies back into SecurityGroups. The marker label is hidden from the SecurityGroup view.
30+
4. The translation is synchronous and stateless — the aggregated API server owns it, so there is no controller, no status reconciliation, and no eventual-consistency window.
31+
32+
### 3.1 Marker-label scoping
33+
34+
Only CiliumNetworkPolicies labelled `sdn.cozystack.io/securitygroup: "true"` are visible through the SecurityGroup API. Policies created by other means (platform tenant-isolation policies, hand-written CiliumNetworkPolicies) are never surfaced and never mutated. `get`/`update`/`patch`/`delete` of an unmarked policy returns `NotFound`. The storage owns the marker label and always re-asserts it on every write (`create`, `update`, `patch`) — the marker is set after any tenant-supplied labels are merged, so a tenant cannot orphan an enforced policy by overwriting the marker through spec labels.
35+
36+
### 3.2 Why an in-tree CiliumNetworkPolicy mirror
37+
38+
`cozystack-api` does not import the `github.com/cilium/cilium` Go module: the current Cilium release pins a Kubernetes minor version newer than this project's `k8s.io/apimachinery` fork supports. Instead the storage uses a minimal in-tree mirror type (`CiliumNetworkPolicy` with a spec equal to `SecurityGroupSpec`) registered with the controller-runtime client at GroupVersion `cilium.io/v2`. Because the SecurityGroup field names and JSON tags match the CiliumNetworkPolicy CRD exactly, marshalling the mirror produces wire-compatible objects and the projection is a near-identity translation in both directions.
39+
40+
## 4. API
41+
42+
```yaml
43+
apiVersion: sdn.cozystack.io/v1alpha1
44+
kind: SecurityGroup
45+
metadata:
46+
name: sg-db
47+
namespace: tenant-a
48+
spec:
49+
endpointSelector:
50+
matchLabels:
51+
app: db
52+
ingress:
53+
- fromEndpoints:
54+
- matchLabels:
55+
app: web
56+
toPorts:
57+
- ports:
58+
- port: "5432"
59+
protocol: TCP
60+
egress:
61+
- toFQDNs:
62+
- matchPattern: "*.apt.example.org"
63+
- toCIDR:
64+
- "10.0.0.0/8"
65+
```
66+
67+
`SecurityGroupSpec` exposes the subset of the CiliumNetworkPolicy rule that the abstraction supports: `endpointSelector`, `ingress[]` (`fromEndpoints`, `fromCIDR`, `toPorts`) and `egress[]` (`toEndpoints`, `toCIDR`, `toFQDNs`, `toPorts`). An empty `ingress`/`egress` list with a set `endpointSelector` denies all traffic in that direction for the selected pods, matching Cilium semantics.
68+
69+
## 5. Backing CiliumNetworkPolicy
70+
71+
The SecurityGroup above projects to:
72+
73+
```yaml
74+
apiVersion: cilium.io/v2
75+
kind: CiliumNetworkPolicy
76+
metadata:
77+
name: sg-db
78+
namespace: tenant-a
79+
labels:
80+
sdn.cozystack.io/securitygroup: "true"
81+
spec:
82+
endpointSelector:
83+
matchLabels:
84+
app: db
85+
ingress:
86+
- fromEndpoints:
87+
- matchLabels:
88+
app: web
89+
toPorts:
90+
- ports:
91+
- port: "5432"
92+
protocol: TCP
93+
egress:
94+
- toFQDNs:
95+
- matchPattern: "*.apt.example.org"
96+
- toCIDR:
97+
- "10.0.0.0/8"
98+
```
99+
100+
## 6. RBAC
101+
102+
- **`cozystack-api` ServiceAccount** — full CRUD on `ciliumnetworkpolicies.cilium.io`, since the storage CRUDs these objects on behalf of tenants.
103+
- **Tenants** — full access to `securitygroups.sdn.cozystack.io` in the base tenant role and read access in the view role, aggregated the same way as `apps.cozystack.io` resources. Tenants never receive any `cilium.io` permission.
104+
105+
## 7. Safety & Interactions
106+
107+
- Policies only apply to pods matched by the SecurityGroup's `endpointSelector`; other pods are unaffected.
108+
- CiliumNetworkPolicy is namespaced, so a SecurityGroup can only affect its own namespace and cannot grant cross-tenant access (network policies are allow-lists for the selected pods' own traffic).
109+
- The marker label keeps SecurityGroups and platform-managed policies strictly separate.
110+
111+
## 8. Future Work
112+
113+
The aggregated-API model is intentionally minimal. The following can be layered on without changing the core projection:
114+
115+
- **Cluster-scope** SecurityGroups projecting to CiliumClusterwideNetworkPolicy.
116+
- An **attachment abstraction** (attach/detach a SecurityGroup to a managed application by reference, with controller-driven pod labelling) for AWS-like ergonomics.
117+
- Reusable **CIDR/FQDN groups** and references between SecurityGroups.
118+
- Exposing more of the CiliumNetworkPolicy rule surface (ICMP, `toServices`, `fromRequires`, `fromCIDRSet`/`toCIDRSet` exceptions) as demand appears.
119+
- **Server-side spec validation** of CIDR ranges, port values and FQDN patterns. Today the storage performs no validation. Only structural CRD-schema violations are caught synchronously when the backing CiliumNetworkPolicy is written (and surface to the caller as a Cilium-level error); a rule that is schema-valid but semantically wrong (a malformed CIDR, an out-of-range port) is accepted, the create/update returns success, and Cilium rejects it asynchronously in the agent — so the policy silently never enforces. For a low-skill-operator platform this is a sharp edge worth closing with SecurityGroup-level validation.

0 commit comments

Comments
 (0)