hackdoc:// HSCloud Docs

Network Policies and netpol library

Network Policies configure connectivity for pods (and by extension, services) within a namespace. Think of it as a simple firewall, specifying which connections (“from pod x”, “to namespace y”, “on port 8080”, etc.) are allowed.

By default, networking is not restricted. A netpol.NetworkPolicy is created to isolate pods, blocking all traffic except for listed exceptions.

NOTE: In the future, we will have a cluster-level “deny by default” policy, and declaring a NetworkPolicy will be required to unisolate pods.

Alas, the Kubernetes NetworkPolicy syntax is confusing, verbose, and it’s very easy to shoot yourself in the foot and accidentally allow or block all traffic. Please use the netpol library instead of raw Kubernetes policies.

Terminology

  • ingress traffic - incoming connections to pods
  • egress traffic - outgoing connections from pods

Don’t confuse ingress network policy with kube.Ingress objects.

Example simple policy

This policy applies to all pods within a namespace, and:

  • allows unrestricted egress traffic
  • allows ingress only:
    • between pods in the namespace
    • from monitoring-cluster namespace - to allow Victoria Metrics (\equiv Prometheus) metrics scraping
    • from ingress-nginx namespace - to allow kube.Ingress objects to work
netpol: ns.Contain(netpol.NetworkPolicy('appname')) {
    ingress: [
        netpol.allowFrom([
            netpol.withinNamespace,
            netpol.namespace('monitoring-cluster'),
            netpol.namespace('ingress-nginx'),
        ]),
    ],
    egress: netpol.unrestricted,
}

Default policy - block by default

An empty policy blocks all ingress and egress traffic to and from all pods within a namespace:

netpol: ns.Contain(netpol.NetworkPolicy('appname')),

This is equivalent to:

netpol: ns.Contain(netpol.NetworkPolicy('appname')) {
    ingress: [],
    egress: [],
}

Empty arrays mean “no exceptions from blocking”.

Allow unrestricted traffic

Use netpol.unrestricted as a shorthand for “no restrictions whatsoever” for all ingress and/or all egress:

netpol: ns.Contain(netpol.NetworkPolicy('appname')) {
    ingress: netpol.unrestricted,
    egress: netpol.unrestricted,
}

Specify allowed traffic

Use netpol.allowFrom() (for ingress) and netpol.allowTo() (for egress) to specify which traffic is allowed. Example:

netpol: ns.Contain(netpol.NetworkPolicy('appname')) {
    ingress: [
        netpol.allowFrom([
            // any pod in the namespace in which NetworkPolicy is defined
            netpol.withinNamespace,
            // any pod in `foo-bar` namespace
            netpol.namespace('foo-bar'),
            // any IP address matching given CIDR
            netpol.cidr('10.14.0.0/16'),
            // any IP address within 185.236.240.32/28, except 185.236.240.40
            netpol.cidr('185.236.240.32/28', except=['185.236.240.40/32']),
        ]),
    ],
    egress: [
        netpol.allowTo([
            // any pod in the cluster
            netpol.withinCluster,
        ]),
    ],
}

Note that allowlists are additive - you cannot block traffic once allowed by a rule.

It’s also possible to select pods matching labels, namespaces matching labels, and matching pods within matching namespaces, but there’s no syntax sugar for these right now. See Kubernetes docs for details.

Specify allowed ports

By default, allowFrom/allowTo() rules apply to traffic on any port. Pass toPorts=[] argument to apply exceptions only to those ports.

Use allowFromWorld(toPorts=[]) to allow unrestricted access to certain ports while blocking others.

Port syntax:

  • toPorts=[2137] - TCP port 2137
  • toPorts=['http'] - port http named on the pod spec
  • toPorts=[22, 80, 443] - a list of ports
  • toPorts=[{ port: 32000, endPort: 32768 }] - a range of ports (must be numeric)
  • toPorts=[{ protocol: 'UDP', port: 53 }] - a UDP port

Example:

netpol: ns.Contain(netpol.NetworkPolicy('appname')) {
    ingress: [
        // allow traffic within namespace on *any* port
        netpol.allowFrom([
            netpol.withinNamespace
        ]),
        // allow traffic from `monitoring-cluster` but only to ports 9090 and 9092
        netpol.allowFrom([
            netpol.namespace('monitoring-cluster')
        ], toPorts=[9090, 9092]),
        // allow traffic from anywhere but only on named port `auth`
        netpol.allowFromWorld(toPorts=['auth']),
    ],
}

Specify pods targetted by a NetworkPolicy

By default, a NetworkPolicy applies to all pods within a namespace. You can narrow down its scope by passing target: with a Deployment/StatefulSet/DaemonSet. Example:

netpol: ns.Contain(netpol.NetworkPolicy('appname')) {
    target: top.deployment,
}

You can also target pods by labels:

netpol: ns.Contain(netpol.NetworkPolicy('appname')) {
    target: { matchLabels: { app: 'foo-bar' } }
}

Gotchas

  • Active TCP connections generally continue working even after a restriction is applied. Deployment restart may be required to see the changes
  • FIXME: Services with type=LoadBalancer are not effectively firewalled and will allow traffic on exposed ports. While <loadBalancerIp>:<port> will appear firewalled, <nodeip>:<nodeport> will still allow traffic.

Resources

The netpol library hopes to make network policies easy to understand.

But, if you want to gain a deeper understanding, you might find these resources helpful: