> ## Documentation Index
> Fetch the complete documentation index at: https://ryvn.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Network Policy

> Control which destinations workloads on Ryvn can reach

Network policy lets you control which destinations workloads on Ryvn can reach. For example, you can:

* Limit an AI agent to the model API it calls, such as `api.anthropic.com`.
* Let a service running in a customer's cloud reach only the vendor APIs it depends on.
* Run generated or untrusted code in an [isolated namespace](#namespace-posture), where it can't reach workload
  identity or the Kubernetes API.

> [!NOTE]
>
> Network policy is in preview and isn't enabled by default. [Contact Ryvn support](https://ryvn.ai/docs/support/contact-us.md) to enable it.

## How it works

A policy selects pods by label and lists the destinations they can reach:

* Domain names (FQDNs), such as `api.anthropic.com`
* IP ranges (CIDRs) outside the cluster, such as `198.51.100.0/24`
* Other workloads in the cluster, selected by pod or namespace labels

Policies are allow-only. Selecting a pod denies its outbound traffic by default, and each rule adds destinations on
top: the pod can reach what the policies selecting it allow, plus cluster DNS, workload identity, and the Kubernetes
API. This applies in every namespace, including ones without a [posture](#namespace-posture). Pods that no policy
selects aren't affected. Only egress (outbound) rules are supported for now.

## Writing a policy

Network policies are Kubernetes resources, so you add them to your Helm chart next to the workloads they apply to, not
to your Ryvn configuration. They use `apiVersion: networking.ryvn.app/v1alpha1`, which is different from the built-in
Kubernetes `networking.k8s.io` NetworkPolicy.

```yaml theme={null}
apiVersion: networking.ryvn.app/v1alpha1
kind: NetworkPolicy
metadata:
  name: agent-egress
spec:
  podSelector:
    matchLabels:
      app: agent
  egress:
    - name: anthropic
      to:
        - domainNames: [api.anthropic.com]
      protocols:
        - tcp: { destinationPort: { number: 443 } }
    - name: redis
      to:
        - pods:
            podSelector:
              matchLabels:
                app: redis
      protocols:
        - tcp: { destinationPort: { number: 6379 } }
```

Each rule has a `name` and a `to` list. Each entry in `to` sets one of these fields:

| Field | Allows |
| - | - |
| `domainNames` | Exact hostnames, such as `api.anthropic.com`. Wildcards aren't supported. |
| `networks` | IP ranges outside the cluster in CIDR notation, IPv4 or IPv6. Pod IPs and Service ClusterIPs never match, so use `pods` or `namespaces` for anything in the cluster. |
| `pods` | Pods matching `podSelector`, in every namespace unless you add a `namespaceSelector`. |
| `namespaces` | Every pod in the namespaces matching a label selector. |

`protocols` limits a rule to TCP or UDP destination ports. Leave it out to allow every port. For `pods` and
`namespaces`, use the port the pod listens on, not the Service port: a Service on port 80 that forwards to 8080 needs
`8080`.

## Namespace posture

A posture sets the default for every pod in a namespace, whether or not a policy selects it. Run agents and other code
you don't trust in an `isolated` namespace: nothing there can reach workload identity or the Kubernetes API, and no
policy can open the Kubernetes API. Workloads that need those, like operators you deploy, can run in a `restricted`
namespace, where they keep cluster DNS, workload identity, and the Kubernetes API. Don't give isolated workloads a cloud
identity, since a domain rule can still reach AWS STS and Microsoft Entra ID.

Set the posture with the `networking.ryvn.app/default-egress-policy` label in the environment's
[`namespaces` list](https://ryvn.ai/docs/guides/namespaces#declaring-namespaces-on-the-environment):

```yaml theme={null}
kind: Environment
metadata:
  name: prod-us
spec:
  namespaces:
    - name: agents
      labels:
        networking.ryvn.app/default-egress-policy: isolated
    - name: operators
      labels:
        networking.ryvn.app/default-egress-policy: restricted
```

| Posture | Pods can reach by default | Policies can open |
| - | - | - |
| `restricted` | Cluster DNS, workload identity, and the Kubernetes API | Any other destination |
| `isolated` | Nothing | Any destination except the Kubernetes API and workload identity (a domain rule can still reach AWS STS and Microsoft Entra ID). A `domainNames` rule also brings DNS back for the pods it selects. |

On Azure, `restricted` and `isolated` namespaces always block instance metadata (`169.254.169.254`) to prevent pods
from using the node's credentials. Use workload identity instead.

In both postures, pods can't reach other pods, including ones in the same namespace, until a policy allows it. Postures
only restrict outbound traffic, so other workloads can still call an isolated agent. Namespaces without the label have
no default restriction.

## Known limitations

* A domain rule allows the IP addresses the domain resolves to, so other hostnames served from the same addresses are
  reachable too.
* On GCP, domains that rotate through many IP addresses, such as large object storage and CDN endpoints, can see
  occasional timeouts. Reuse connections, or also allow the provider's published IP ranges with `networks`.
* On GCP, a pod with a domain rule can look up any name in DNS, even though it can only connect to allowed
  destinations, so data can still leave inside DNS queries.
* On GCP, a `networks` range that includes the cluster's nodes, such as `0.0.0.0/0`, also opens every node on the
  rule's ports. List only the ports you need.
* On Azure, a new pod with a domain rule may fail DNS lookups for its first few seconds, until Azure's DNS proxy learns
  about the pod. This can happen on every rollout, scale-up, or Job run.
* The `isolated` posture can't block domain-based identity endpoints, such as AWS STS for IRSA and Microsoft Entra ID
  for Azure workload identity. Enforce this through identity, not network policy: don't link the service accounts of
  isolated workloads to an IAM role or an Azure managed identity.
* Policies don't restrict host-networked pods, and `pods` and `namespaces` rules don't select them as destinations.
