container security

Implementing Container Network Policies with Calico

Enforce Kubernetes network segmentation using Calico CNI network policies and global network policies to control pod-to-pod traffic, restrict egress, and implement zero-trust microsegmentation.

calicocnicontainer-securitykubernetesmicrosegmentationnetwork-policy
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Calico provides Kubernetes-native and extended network policy enforcement through its CNI plugin. This skill covers creating and auditing Calico NetworkPolicy and GlobalNetworkPolicy resources to implement pod-to-pod traffic control, namespace isolation, egress restrictions, and DNS-based policy rules using calicoctl and the Kubernetes API.

When to Use

  • When deploying or configuring implementing container network policies with calico capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Kubernetes cluster with Calico CNI installed
  • Python 3.9+ with kubernetes client library
  • calicoctl CLI tool installed and configured
  • kubectl access with RBAC permissions for network policy management

Steps

Step 1: Audit Existing Network Policies

Use calicoctl and kubectl to inventory current network policies and identify unprotected namespaces.

Step 2: Implement Default-Deny Policies

Create default-deny ingress and egress policies per namespace as a zero-trust baseline.

Step 3: Create Workload-Specific Allow Rules

Define granular allow rules for legitimate pod-to-pod and pod-to-service communication.

Step 4: Validate Policy Enforcement

Test connectivity between pods to verify policies are correctly enforced.

Expected Output

JSON audit report listing all network policies, unprotected namespaces, policy rule counts, and connectivity test results.

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.9 KB

API Reference: Implementing Container Network Policies with Calico

calicoctl Commands

# List network policies across all namespaces
calicoctl get networkpolicy --all-namespaces -o json
 
# List global network policies
calicoctl get globalnetworkpolicy -o json
 
# Check Calico node status
calicoctl node status
 
# Apply a Calico network policy
calicoctl apply -f policy.yaml
 
# Get workload endpoints
calicoctl get workloadendpoint -o wide
 
# Check IP pool configuration
calicoctl get ippool -o json

Kubernetes NetworkPolicy vs Calico

Feature K8s NetworkPolicy Calico NetworkPolicy Calico GlobalNetworkPolicy
Scope Namespace Namespace Cluster-wide
Selector Pod labels Pod + service account All workloads + host endpoints
Rule types Ingress, Egress Ingress, Egress Ingress, Egress
DNS policy No Yes Yes
Order/Priority No Yes (order field) Yes (order field)
CIDR ranges Yes Yes Yes

Default-Deny Policy Template

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress

Python kubernetes Client

from kubernetes import client, config
 
config.load_kube_config()
net_v1 = client.NetworkingV1Api()
policies = net_v1.list_network_policy_for_all_namespaces()
for p in policies.items:
    print(p.metadata.name, p.metadata.namespace)

Install: pip install kubernetes

References

Scripts 1

agent.py7.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for implementing container network policies with Calico.

Audits Kubernetes network policies, identifies unprotected
namespaces, validates Calico policy enforcement, and generates
default-deny baseline policy manifests.
"""

import argparse
import json
import os
import subprocess
from datetime import datetime
from pathlib import Path

try:
    from kubernetes import client, config
except ImportError:
    client = None
    config = None


DEFAULT_DENY_INGRESS = {
    "apiVersion": "networking.k8s.io/v1",
    "kind": "NetworkPolicy",
    "metadata": {"name": "default-deny-ingress"},
    "spec": {"podSelector": {}, "policyTypes": ["Ingress"]},
}

DEFAULT_DENY_EGRESS = {
    "apiVersion": "networking.k8s.io/v1",
    "kind": "NetworkPolicy",
    "metadata": {"name": "default-deny-egress"},
    "spec": {"podSelector": {}, "policyTypes": ["Egress"]},
}


class CalicoNetworkPolicyAgent:
    """Audits and manages Calico network policies on Kubernetes."""

    def __init__(self, kubeconfig=None, output_dir="./calico_policy_audit"):
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.findings = []
        self.v1 = None
        self.net_v1 = None
        if client and config:
            try:
                if kubeconfig:
                    config.load_kube_config(config_file=kubeconfig)
                else:
                    config.load_kube_config()
                self.v1 = client.CoreV1Api()
                self.net_v1 = client.NetworkingV1Api()
            except Exception:
                pass

    def _run_calicoctl(self, args):
        """Run calicoctl command and return parsed output."""
        try:
            result = subprocess.run(
                ["calicoctl"] + args, capture_output=True, text=True, timeout=30
            )
            return result.stdout, result.returncode
        except FileNotFoundError:
            return "", 1
        except subprocess.TimeoutExpired:
            return "", 1

    def list_k8s_network_policies(self):
        """List all Kubernetes NetworkPolicy resources across namespaces."""
        if not self.net_v1:
            return []
        policies = self.net_v1.list_network_policy_for_all_namespaces()
        return [{"name": p.metadata.name, "namespace": p.metadata.namespace,
                 "pod_selector": p.spec.pod_selector.match_labels or {},
                 "policy_types": p.spec.policy_types or [],
                 "ingress_rules": len(p.spec.ingress or []),
                 "egress_rules": len(p.spec.egress or [])}
                for p in policies.items]

    def list_calico_policies(self):
        """List Calico-specific NetworkPolicy and GlobalNetworkPolicy resources."""
        policies = []
        stdout, rc = self._run_calicoctl(["get", "networkpolicy", "-o", "json", "--all-namespaces"])
        if rc == 0 and stdout.strip():
            try:
                data = json.loads(stdout)
                items = data.get("items", [data]) if "items" in data else [data]
                for item in items:
                    meta = item.get("metadata", {})
                    policies.append({"kind": "NetworkPolicy", "name": meta.get("name"),
                                     "namespace": meta.get("namespace")})
            except json.JSONDecodeError:
                pass

        stdout, rc = self._run_calicoctl(["get", "globalnetworkpolicy", "-o", "json"])
        if rc == 0 and stdout.strip():
            try:
                data = json.loads(stdout)
                items = data.get("items", [data]) if "items" in data else [data]
                for item in items:
                    policies.append({"kind": "GlobalNetworkPolicy",
                                     "name": item.get("metadata", {}).get("name")})
            except json.JSONDecodeError:
                pass
        return policies

    def find_unprotected_namespaces(self):
        """Identify namespaces without any network policies."""
        if not self.v1 or not self.net_v1:
            return []
        namespaces = [ns.metadata.name for ns in self.v1.list_namespace().items]
        policies = self.net_v1.list_network_policy_for_all_namespaces()
        protected = {p.metadata.namespace for p in policies.items}
        system_ns = {"kube-system", "kube-public", "kube-node-lease", "calico-system"}
        unprotected = [ns for ns in namespaces if ns not in protected and ns not in system_ns]
        for ns in unprotected:
            self.findings.append({"severity": "high", "type": "Unprotected Namespace",
                                  "detail": f"Namespace '{ns}' has no network policies"})
        return unprotected

    def generate_default_deny(self, namespace):
        """Generate default-deny policy manifests for a namespace."""
        ingress = {**DEFAULT_DENY_INGRESS, "metadata": {**DEFAULT_DENY_INGRESS["metadata"],
                                                         "namespace": namespace}}
        egress = {**DEFAULT_DENY_EGRESS, "metadata": {**DEFAULT_DENY_EGRESS["metadata"],
                                                       "namespace": namespace}}
        return {"namespace": namespace, "ingress_policy": ingress, "egress_policy": egress}

    def check_calico_node_status(self):
        """Check Calico node status using calicoctl."""
        stdout, rc = self._run_calicoctl(["node", "status"])
        if rc == 0:
            return {"status": "healthy", "output": stdout[:500]}
        return {"status": "unavailable"}

    def test_connectivity(self, src_pod, dst_pod, namespace, port=80):
        """Test pod-to-pod connectivity using kubectl exec."""
        try:
            result = subprocess.run(
                ["kubectl", "exec", src_pod, "-n", namespace, "--",
                 "wget", "--spider", "--timeout=3", f"http://{dst_pod}:{port}"],
                capture_output=True, text=True, timeout=10,
            )
            return {"src": src_pod, "dst": dst_pod, "port": port,
                    "connected": result.returncode == 0}
        except (FileNotFoundError, subprocess.TimeoutExpired):
            return {"src": src_pod, "dst": dst_pod, "connected": None, "error": "test failed"}

    def generate_report(self):
        k8s_policies = self.list_k8s_network_policies()
        calico_policies = self.list_calico_policies()
        unprotected = self.find_unprotected_namespaces()
        node_status = self.check_calico_node_status()
        deny_manifests = [self.generate_default_deny(ns) for ns in unprotected[:5]]

        report = {
            "report_date": datetime.utcnow().isoformat(),
            "k8s_network_policies": {"count": len(k8s_policies), "policies": k8s_policies},
            "calico_policies": {"count": len(calico_policies), "policies": calico_policies},
            "unprotected_namespaces": unprotected,
            "calico_node_status": node_status,
            "recommended_deny_policies": deny_manifests,
            "findings": self.findings,
            "total_findings": len(self.findings),
        }
        out = self.output_dir / "calico_policy_report.json"
        with open(out, "w") as f:
            json.dump(report, f, indent=2)
        print(json.dumps(report, indent=2))
        return report


def main():
    parser = argparse.ArgumentParser(
        description="Audit and manage Calico network policies on Kubernetes"
    )
    parser.add_argument("--kubeconfig", default=None,
                        help="Path to kubeconfig file")
    parser.add_argument("--output-dir", default="./calico_policy_audit",
                        help="Output directory for report")
    args = parser.parse_args()

    os.makedirs(args.output_dir, exist_ok=True)
    agent = CalicoNetworkPolicyAgent(kubeconfig=args.kubeconfig,
                                     output_dir=args.output_dir)
    agent.generate_report()


if __name__ == "__main__":
    main()
Keep exploring