Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ uds-docs/**
**.backup
**/.playwright/**
**/.playwright

coverage/**
Comment thread
mjnagel marked this conversation as resolved.
251 changes: 251 additions & 0 deletions docs/dev/authorization-policy-generation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
## Overview

This guide describes how Istio AuthorizationPolicies are generated from the UDSPackage CR by the UDS Operator. These **ALLOW** policies are primarily used to enable ingress security within an Istio Ambient Mesh environment.

The code responsible for generating these policies can be found [here](https://github.com/defenseunicorns/uds-core/blob/main/src/pepr/operator/controllers/network/authorizationPolicies.ts) and includes support for three rule types:
- `allow`: Direct ingress rules for services.
- `expose`: Gateway-based ingress exposure.
- `monitor`: Restricts access to metrics endpoints.

Each rule is processed individually to generate a single Istio AuthorizationPolicy.

---

## Policy Generation Flow

1. **Input Collection**
- The operator reads the `spec.network.allow`, `spec.network.expose`, and `spec.monitor` fields from a UDSPackage.

2. **Allow Rule Processing**
- Sources are computed based on `remoteGenerated`, `remoteNamespace`, and `remoteServiceAccount`.
- Port info is collected from `port` and `ports`.
- If `remoteServiceAccount` is present, a `principal` source is used, overriding namespace restrictions.

3. **Expose Rule Processing**
- Uses `port` or `targetPort` for port resolution.
- Sources are determined by the selected gateway:
- Admin gateway → `cluster.local/ns/istio-admin-gateway/sa/admin-ingressgateway`
- Tenant gateway (default) → `cluster.local/ns/istio-tenant-gateway/sa/tenant-ingressgateway`

4. **Monitor Rule Processing**
- Each monitor rule generates a policy allowing access from `monitoring` namespace to a specific port.

5. **Policy Naming**
- All policies start with `protect-<pkgName>-<rule-derived-name>`.
- `allow` rules use either the `description` or a combination of selector and remote fields.
- `expose` rules follow `ingress-<port>-<selector>-istio-<gateway>-gateway`.

6. **Policy Application**
- Policies are applied via `K8s(AuthorizationPolicy).Apply()` with force enabled.
- `purgeOrphans` removes outdated or unused policies from previous generations.

---

## Development Tips

- **Rule Deduplication**: Currently, even identical selectors in different rules generate separate policies.
- **Troubleshooting**: Enable debug logging to inspect which policy is generated and applied.
- **Testing**: Use test UDSPackages with different `remoteGenerated` and gateway values to validate behavior.
- **Best Practices**:
- Avoid overly broad allow rules (e.g., `remoteGenerated: Anywhere`) unless absolutely necessary.
- Prefer using `remoteServiceAccount` for precise identity-based access.

---

## Example Use Cases

### Example 1: Allow Ingress from a Specific Namespace (No Selector)

```yaml
spec:
network:
allow:
- direction: Ingress
remoteNamespace: "external-app"
port: 8080
```

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: protect-my-app-ingress-external-app
namespace: my-app-namespace
labels:
uds/package: my-app
uds/generation: "1"
spec:
action: ALLOW
rules:
- from:
- source:
namespaces: ["external-app"]
to:
- operation:
ports: ["8080"]
```

### Example 2: Allow Ingress Only to a Specific Pod Selector

```yaml
spec:
network:
allow:
- direction: Ingress
remoteNamespace: "external-app"
selector:
app: "frontend"
port: 8080
```

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: protect-my-app-ingress-frontend
namespace: my-app-namespace
labels:
uds/package: my-app
uds/generation: "1"
spec:
action: ALLOW
selector:
matchLabels:
app: "frontend"
rules:
- from:
- source:
namespaces: ["external-app"]
to:
- operation:
ports: ["8080"]
```

### Example 3: Intra-Namespace Rule Without Port

```yaml
spec:
network:
allow:
- direction: Ingress
remoteGenerated: IntraNamespace
```

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: protect-loki-ingress-all
namespace: loki
labels:
uds/package: loki
uds/generation: "1"
spec:
action: ALLOW
rules:
- from:
- source:
namespaces: ["loki"]
```

### Example 4: Allow Anywhere Rule (No Namespace Restriction)

```yaml
spec:
network:
allow:
- direction: Ingress
remoteGenerated: Anywhere
port: 80
```

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: protect-myapp-ingress-all
namespace: my-namespace
labels:
uds/package: myapp
uds/generation: "1"
spec:
action: ALLOW
rules:
- from: []
to:
- operation:
ports: ["80"]
```

### Example 5: Expose Rule with Gateway Specification

```yaml
spec:
network:
expose:
- port: 8080
targetPort: 9090
selector:
app: "backend"
gateway: Admin
```

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: protect-my-app-ingress-9090-backend-istio-admin-gateway
namespace: my-app-namespace
labels:
uds/package: my-app
uds/generation: "1"
spec:
action: ALLOW
selector:
matchLabels:
app: "backend"
rules:
- from:
- source:
principals: ["cluster.local/ns/istio-admin-gateway/sa/admin-ingressgateway"]
to:
- operation:
ports: ["9090"]
```

### Example 6: Monitor Rule for Securing a Metrics Endpoint

```yaml
spec:
monitor:
- description: Metrics
podSelector:
app.kubernetes.io/name: grafana
portName: service
selector:
app.kubernetes.io/name: grafana
targetPort: 3000
```

```yaml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: protect-grafana-ingress-grafana-istio-tenant-gateway
namespace: grafana
labels:
uds/package: grafana
uds/generation: "1"
spec:
action: ALLOW
selector:
matchLabels:
app.kubernetes.io/name: grafana
rules:
- from:
- source:
namespaces: ["monitoring"]
to:
- operation:
ports: ["3000"]
```
3 changes: 3 additions & 0 deletions docs/reference/configuration/Single Sign-On/auth-service.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ The UDS Operator uses the first `redirectUris` to populate the `match.prefix` ho
:::

For a complete example, see [app-authservice-tenant.yaml](https://github.com/defenseunicorns/uds-core/blob/main/src/test/app-authservice-tenant.yaml)

## Limitations:
Authservice is intended for simple, basic protection scenarios where an absolute level of protection is acceptable. For more advanced authentication requirements, you should implement authentication directly in your application or via a more comprehensive solution.
99 changes: 99 additions & 0 deletions docs/reference/configuration/authorization-policies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
title: How Authorization Policies Protect Your Services
sidebar:
order: 3
---

In clusters running Istio Ambient Mesh, UDS‑Core enforces **ingress network security** using Istio **ALLOW** AuthorizationPolicies. These policies are automatically generated for each application package you define with a [UDS Package](https://uds.defenseunicorns.com/reference/configuration/uds-operator/package/) resource.

Comment thread
noahpb marked this conversation as resolved.
This document explains what this means for you as an application developer and how to take full advantage of the built-in security model.

---

## Key Takeaways

- **Ingress is denied by default.** UDS Core only allows what you explicitly configure using `allow` and `expose` rules.

- **AuthorizationPolicies are ALLOW-based**, which means you must write **DENY** rules separately if you want to restrict internal traffic further.

- **Use `remoteServiceAccount` wherever possible.** This provides the most secure and identity-based access control.

- **Expose rules use gateways** to control what traffic enters your application. You can choose between:
- **Tenant Gateway** (default)
- **Admin Gateway** (used only when absolutely necessary)

- **Monitoring ports are automatically secured** using rules that only allow the `monitoring` namespace to scrape metrics.

---

## Best Practices for Secure Configuration

### 1. Lock Down Ingress With `allow`

```yaml
spec:
network:
allow:
- direction: Ingress
remoteNamespace: "external-app"
remoteServiceAccount: "my-client"
port: 8080
```

> This ensures that only a workload running as this specific service account in another namespace can access your service.

### 2. Expose Your Service Safely

```yaml
spec:
network:
expose:
- port: 80
targetPort: 8080
gateway: Tenant
```

> This exposes your service at port 80 through the tenant gateway and maps it to your app’s port 8080.

### 3. Enable Safe Monitoring

```yaml
spec:
monitor:
- targetPort: 3000
selector:
app.kubernetes.io/name: grafana
```

> This creates a rule that allows only Prometheus (from the `monitoring` namespace) to scrape your service.

---

## Authservice Guidance

If you're using Authservice, be aware that it is **only appropriate for simple access scenarios**, such as:

- Protecting web UIs or dashboards
- Cases where access can be fully granted or denied with no granularity

---

## How Istio Evaluates Policies

Istio checks **DENY policies first**, then **ALLOW policies**.

- The operator creates ALLOW policies to admit approved ingress traffic.
- You should create your own DENY policies for more fine-grained control.

More info: [Istio Authorization Policy Evaluation](https://istio.io/latest/docs/concepts/security/#authorization-policy)

---

## Summary

- Ingress is denied by default.
- You allow ingress by defining `allow` or `expose` rules in your UDS Package resource definition.
- You can further tighten security using DENY policies.
- Use `remoteServiceAccount` for the strongest protection.
- Authservice is good for simple cases only—use stronger methods for complex needs.

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
title: Exemptions CR (v1alpha1)
tableOfContents:
maxHeadingLevel: 6
sidebar:
order: 11
---
<a id="Exemptions"></a>
<div style="margin-left: 20px; padding-top: 30px;">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
title: Packages CR (v1alpha1)
tableOfContents:
maxHeadingLevel: 6
sidebar:
order: 10
---
<a id="Packages"></a>
<div style="margin-left: 20px; padding-top: 30px;">
Expand Down Expand Up @@ -126,7 +124,7 @@ sidebar:
</tr>
</thead>
<tbody>
<tr><td style="white-space: nowrap;">description</td><td style="white-space: nowrap;">string</td><td>A description of the policy, this will become part of the policy name</td></tr><tr><td style="white-space: nowrap;">direction</td><td style="white-space: nowrap;">string (enum):<ul><li><code>Ingress</code></li><li><code>Egress</code></li></ul></td><td>The direction of the traffic</td></tr><tr><td style="white-space: nowrap;">labels</td><td style="white-space: nowrap;"></td><td>The labels to apply to the policy</td></tr><tr><td style="white-space: nowrap;">podLabels</td><td style="white-space: nowrap;"></td><td>Deprecated: use selector</td></tr><tr><td style="white-space: nowrap;">port</td><td style="white-space: nowrap;">number</td><td>The port to allow (protocol is always TCP)</td></tr><tr><td style="white-space: nowrap;">ports</td><td style="white-space: nowrap;">number[]</td><td>A list of ports to allow (protocol is always TCP)</td></tr><tr><td style="white-space: nowrap;">remoteCidr</td><td style="white-space: nowrap;">string</td><td>Custom generated policy CIDR</td></tr><tr><td style="white-space: nowrap;">remoteGenerated</td><td style="white-space: nowrap;">string (enum):<ul><li><code>KubeAPI</code></li><li><code>KubeNodes</code></li><li><code>IntraNamespace</code></li><li><code>CloudMetadata</code></li><li><code>Anywhere</code></li></ul></td><td>Custom generated remote selector for the policy</td></tr><tr><td style="white-space: nowrap;">remoteNamespace</td><td style="white-space: nowrap;">string</td><td>The remote namespace to allow traffic to/from. Use * or empty string to allow all namespaces</td></tr><tr><td style="white-space: nowrap;">remotePodLabels</td><td style="white-space: nowrap;"></td><td>Deprecated: use remoteSelector</td></tr><tr><td style="white-space: nowrap;">remoteSelector</td><td style="white-space: nowrap;"></td><td>The remote pod selector labels to allow traffic to/from</td></tr><tr><td style="white-space: nowrap;">selector</td><td style="white-space: nowrap;"></td><td>Labels to match pods in the namespace to apply the policy to. Leave empty to apply to all pods in the namespace</td></tr>
<tr><td style="white-space: nowrap;">description</td><td style="white-space: nowrap;">string</td><td>A description of the policy, this will become part of the policy name</td></tr><tr><td style="white-space: nowrap;">direction</td><td style="white-space: nowrap;">string (enum):<ul><li><code>Ingress</code></li><li><code>Egress</code></li></ul></td><td>The direction of the traffic</td></tr><tr><td style="white-space: nowrap;">labels</td><td style="white-space: nowrap;"></td><td>The labels to apply to the policy</td></tr><tr><td style="white-space: nowrap;">podLabels</td><td style="white-space: nowrap;"></td><td>Deprecated: use selector</td></tr><tr><td style="white-space: nowrap;">port</td><td style="white-space: nowrap;">number</td><td>The port to allow (protocol is always TCP)</td></tr><tr><td style="white-space: nowrap;">ports</td><td style="white-space: nowrap;">number[]</td><td>A list of ports to allow (protocol is always TCP)</td></tr><tr><td style="white-space: nowrap;">remoteCidr</td><td style="white-space: nowrap;">string</td><td>Custom generated policy CIDR</td></tr><tr><td style="white-space: nowrap;">remoteGenerated</td><td style="white-space: nowrap;">string (enum):<ul><li><code>KubeAPI</code></li><li><code>KubeNodes</code></li><li><code>IntraNamespace</code></li><li><code>CloudMetadata</code></li><li><code>Anywhere</code></li></ul></td><td>Custom generated remote selector for the policy</td></tr><tr><td style="white-space: nowrap;">remoteNamespace</td><td style="white-space: nowrap;">string</td><td>The remote namespace to allow traffic to/from. Use * or empty string to allow all namespaces</td></tr><tr><td style="white-space: nowrap;">remotePodLabels</td><td style="white-space: nowrap;"></td><td>Deprecated: use remoteSelector</td></tr><tr><td style="white-space: nowrap;">remoteSelector</td><td style="white-space: nowrap;"></td><td>The remote pod selector labels to allow traffic to/from</td></tr><tr><td style="white-space: nowrap;">remoteServiceAccount</td><td style="white-space: nowrap;">string</td><td>The remote service account to restrict incoming traffic from within the remote namespace. Only valid for Ingress rules.</td></tr><tr><td style="white-space: nowrap;">selector</td><td style="white-space: nowrap;"></td><td>Labels to match pods in the namespace to apply the policy to. Leave empty to apply to all pods in the namespace</td></tr>
</tbody>
</table>
</div>
Expand Down
Loading