WIUNIX ACADEMY
Kubernetes NetworkPolicy: prove default deny, DNS and one allowed path
Build a disposable labeled namespace, deny traffic, allow explicit UDP/TCP DNS and one client-to-API flow, then test allowed and forbidden peers.
Make this guide yoursYOUR SYSTEM. YOUR STEPS.
Where are you working?
Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.
USE YOUR OWN VALUES
Configure this tutorial
Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.
Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.
Set values for my environment4 editable values
THE OUTCOME
What will you have at the end?
Demonstrate actual CNI enforcement and the additive ingress/egress policy model in a disposable lab.
Supported operating systems and versions
Documented disposable lab. No cluster policy was applied during authoring; CNI-specific enforcement remains a required reader test. The current tutorial form accepts simple identifiers of at most32characters. This is a form limitation, not the Kubernetes resource-name limit. Never truncate or rename a live object to fit. For a longer existing resource/context name, follow this same diagnostic sequence in the operator-owned direct CLI/editor workflow with its exact original name; no silent default substitution.
Before you begin
- Cluster-owner authorization and a CNI with documented NetworkPolicy enforcement
- New disposable namespace; no production workloads
- Approved server/tool images preserving the sample command behavior; kubectl permissions to create/delete lab resources
- The current tutorial form accepts simple identifiers of at most32characters. This is a form limitation, not the Kubernetes resource-name limit. Never truncate or rename a live object to fit. For a longer existing resource/context name, follow this same diagnostic sequence in the operator-owned direct CLI/editor workflow with its exact original name; no silent default substitution.
LET’S BUILD
Prepare the selected operator host
These are operator-host packages only. Review the transaction before approving; do not install tools inside application containers. The cluster, server and application binaries must already exist with owner-approved versions. Use Bash for the following panels. Package installation changes this selected host but starts no new application server.
Use an existing Linux kubectl within one minor version of every API-server version; inspect the actual server version in the authorized context before proceeding. Do not install kubelet/kubeadm or overwrite cluster client repositories for this diagnosis.
Selected operator host; approved package transaction
sudo apt-get update
sudo apt-get install --no-install-recommends ca-certificates curl bash coreutils
cat /etc/os-release
command -v bash curl timeout
command -v kubectl
kubectl version --client --output=yamlThe OS/version matches the selection, required tools resolve, and existing application/client versions satisfy the stated compatibility boundary. Stop if a binary is missing or incompatible.
Preserve host security and existing paths
Ubuntu/Debian may use AppArmor and host firewall rules. This workflow opens no inbound host port and changes no security profile. For denied file/network access, inspect the owning service logs and policy with its administrator. Keep existing executable/config paths; public path fields are reviewed example locations, not discovered installation paths.
A policy object is not proof of enforcement
The cluster CNI must implement NetworkPolicy. An accepted API object on an unsupported CNI can leave traffic open. Standard policies are additive: a broad allow in another policy can permit the same traffic. Both source egress and destination ingress must allow a connection when both endpoints are isolated.
Run only in a new disposable namespace, never add default deny to an existing production namespace as a learning exercise. This lab uses ordinary non-hostNetwork Pods, TCP HTTP and CoreDNS Pod selectors; NodeLocal DNSCache and provider-specific policy layers require a different DNS rule.
LET’S BUILD
Check context, CNI and name absence
Confirm the CNI documentation and cluster-owner approval before proceeding. The absence check must show NotFound; any existing namespace means choose another reviewed name. Do not continue after Forbidden or a connection error. Use an already approved digest-pinned HTTP server and tool image in the manifest, reviewed by the cluster owner; the readable tags below are lab examples, not supply-chain acceptance.
Operator terminal; authorized lab context
kubectl config get-contexts 'lab'
kubectl --context='lab' get namespace 'academy-lab'
kubectl --context='lab' -n kube-system get pods -l k8s-app=kube-dns --show-labels
kubectl --context='lab' auth can-i create namespacesAn authorized context, enforcing CNI, CoreDNS labels and absent lab namespace are confirmed.
LET’S BUILD
Create only the new lab manifest
Use a new working directory; exclusive creation prevents overwriting an existing file. The next YAML is file content. Review it before submitting to the API. Resource requests/limits keep this small fixture bounded but do not prove image safety.
Operator terminal; authorized lab context
umask 077
mkdir '/tmp/academy-netpol' && cd '/tmp/academy-netpol' || exit 1
(set -C; : > lab.yaml) || exit 1
${EDITOR:-vi} lab.yamlA new private directory and empty lab.yaml exist for the reviewed file content.
LET’S BUILD
Define server, allowed client and forbidden peer
All Pods live in the new namespace. The server and tools are separate roles. Replace image references with your approved pinned equivalents before applying; preserve their port/command behavior. The BusyBox shell sleeps so each connectivity test starts a fresh connection.
File contents; save at the displayed filename
apiVersion: v1
kind: Pod
metadata:
name: api
namespace: academy-lab
labels: {app: api}
spec:
containers:
- name: web
image: registry.k8s.io/e2e-test-images/agnhost:2.53
args: [netexec, --http-port=8080]
ports: [{containerPort: 8080}]
resources:
requests: {cpu: 10m, memory: 32Mi}
limits: {cpu: 200m, memory: 128Mi}
---
apiVersion: v1
kind: Service
metadata:
name: api
namespace: academy-lab
spec:
selector: {app: api}
ports: [{port: 8080, targetPort: 8080}]
---
apiVersion: v1
kind: Pod
metadata:
name: client
namespace: academy-lab
labels: {role: client}
spec:
containers:
- name: tools
image: busybox:1.37
command: [sh, -c, 'sleep 86400']
resources:
requests: {cpu: 5m, memory: 8Mi}
limits: {cpu: 100m, memory: 32Mi}
---
apiVersion: v1
kind: Pod
metadata:
name: stranger
namespace: academy-lab
labels: {role: stranger}
spec:
containers:
- name: tools
image: busybox:1.37
command: [sh, -c, 'sleep 86400']
resources:
requests: {cpu: 5m, memory: 8Mi}
limits: {cpu: 100m, memory: 32Mi}
Only the named disposable namespace and four lab resources are declared.