Applying and Updating Network Policies Programmatically

We've explored the structure of the NetworkPolicy resource and common patterns for defining security rules. Now, let's focus on how to actually create and modify these policies in your cluster using client-go. The process closely mirrors how we managed Service and Ingress resources.

Applying (Creating) Network Policies

Creating a Network Policy involves constructing the networkingv1.NetworkPolicy struct in Go, populating its metadata and spec fields according to the desired security rules (like the patterns discussed previously), and then using the Create method.

  1. Get the Client: Obtain the NetworkPolicy client for the target namespace:

    networkPoliciesClient := clientset.NetworkingV1().NetworkPolicies(namespace)
  2. Define the Policy: Construct the *networkingv1.NetworkPolicy object in Go, carefully defining metadata.name, metadata.namespace, spec.podSelector, spec.policyTypes, and the spec.ingress and/or spec.egress rules. Use helpers like metav1.LabelSelector, v1.ProtocolTCP, intstr.FromInt() etc. as needed.

    // Example: Define a simple default-deny ingress policy spec (from previous section)
    policySpec := &networkingv1.NetworkPolicy{
        ObjectMeta: metav1.ObjectMeta{
            Name:      "default-deny-ingress-programmatic",
            Namespace: targetNamespace, // specify the namespace
        },
        Spec: networkingv1.NetworkPolicySpec{
            PodSelector: metav1.LabelSelector{}, // Select all pods
            PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
            // Ingress: []networkingv1.NetworkPolicyIngressRule{}, // Empty or omitted = deny all
        },
    }
  3. Call Create: Use the client to create the resource in the cluster.

    log.Printf("Creating NetworkPolicy '%s' in namespace '%s'...\n", policySpec.Name, policySpec.Namespace)
    createdPolicy, err := networkPoliciesClient.Create(context.TODO(), policySpec, metav1.CreateOptions{})
    if err != nil {
        if k8serrors.IsAlreadyExists(err) {
            log.Printf("NetworkPolicy '%s' already exists.\n", policySpec.Name)
            // Handle existing policy if necessary (e.g., Get it)
        } else {
            log.Fatalf("Error creating NetworkPolicy '%s': %s\n", policySpec.Name, err.Error())
        }
    } else {
        log.Printf("Successfully created NetworkPolicy '%s'.\n", createdPolicy.Name)
    }

Updating Network Policies

Modifying an existing Network Policy follows the same Get-Modify-Update pattern we used for Ingress resources, including the critical need to handle potential conflicts using retry.RetryOnConflict.

  1. Get: Retrieve the current NetworkPolicy using networkPoliciesClient.Get(...).

  2. Modify: Change the Go struct object in memory. For example:

    • Add a new rule to the spec.ingress or spec.egress slice.

    • Modify an existing rule's from, to, or ports.

    • Change the spec.podSelector.

    • Update spec.policyTypes.

  3. Update: Call networkPoliciesClient.Update(...) with the modified object. Use retry.RetryOnConflict to automatically handle IsConflict errors by retrying the Get-Modify-Update sequence.

Example: Adding an Ingress Rule to an Existing Policy

Let's assume a "default-deny-ingress" policy exists, and we want to update it to allow ingress from Pods labeled role=monitoring on TCP port 9090.

// Assume 'clientset', 'policyName', 'namespace' are defined

networkPoliciesClient := clientset.NetworkingV1().NetworkPolicies(namespace)

log.Printf("Attempting to update NetworkPolicy '%s' to allow monitoring ingress...\n", policyName)

// Use RetryOnConflict for robust updates
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
    // Step 1: Get the latest version
    currentPolicy, getErr := networkPoliciesClient.Get(context.TODO(), policyName, metav1.GetOptions{})
    if k8serrors.IsNotFound(getErr) {
        log.Printf("NetworkPolicy '%s' not found. Cannot update.", policyName)
        return errors.New("policy not found") // Stop retrying if not found
    }
    if getErr != nil {
        log.Printf("Failed to get NetworkPolicy '%s': %v. Retrying...", policyName, getErr)
        return getErr // Trigger retry
    }
    log.Printf("Retrieved NetworkPolicy '%s' with resourceVersion %s\n", currentPolicy.Name, currentPolicy.ResourceVersion)

    // Step 2: Modify the retrieved object
    policyUpdated := false

    // Ensure PolicyTypes includes Ingress (it should if it's default deny ingress)
    hasIngressType := false
    for _, pType := range currentPolicy.Spec.PolicyTypes {
        if pType == networkingv1.PolicyTypeIngress {
            hasIngressType = true
            break
        }
    }
    if !hasIngressType {
         log.Printf("PolicyType 'Ingress' not found in policy '%s'. Adding it.", policyName)
        currentPolicy.Spec.PolicyTypes = append(currentPolicy.Spec.PolicyTypes, networkingv1.PolicyTypeIngress)
        policyUpdated = true
    }


    // Define the new rule to add
    tcp := v1.ProtocolTCP
    port9090 := intstr.FromInt(9090)
    newIngressRule := networkingv1.NetworkPolicyIngressRule{
        From: []networkingv1.NetworkPolicyPeer{
            {
                PodSelector: &metav1.LabelSelector{
                    MatchLabels: map[string]string{"role": "monitoring"},
                },
                 // Note: Omitting NamespaceSelector defaults to allowing from *any* namespace
                 // if you only want monitoring from the *same* namespace, add:
                 // NamespaceSelector: &metav1.LabelSelector{}, // Empty selects policy's namespace
            },
        },
        Ports: []networkingv1.NetworkPolicyPort{
            {Protocol: &tcp, Port: &port9090},
        },
    }

    // Check if an identical rule already exists (optional, avoids redundant updates)
    ruleExists := false
    for _, existingRule := range currentPolicy.Spec.Ingress {
         // Simple check for demonstration; deep equality check might be needed
         // This check is basic and might not correctly identify identical rules in complex cases
         if len(existingRule.From) == 1 && len(existingRule.From[0].PodSelector.MatchLabels) == 1 &&
            existingRule.From[0].PodSelector.MatchLabels["role"] == "monitoring" &&
            len(existingRule.Ports) == 1 && *existingRule.Ports[0].Port == *port9090 {
             ruleExists = true
             log.Println("Monitoring ingress rule already seems to exist.")
             break
         }
    }


    // Add the new rule if it doesn't exist
    if !ruleExists {
        currentPolicy.Spec.Ingress = append(currentPolicy.Spec.Ingress, newIngressRule)
        policyUpdated = true
        log.Println("Adding monitoring ingress rule.")
    }


    // Step 3: Call Update only if changes were made
    if !policyUpdated {
        log.Println("No modifications needed. Skipping update.")
        return nil // Stop retrying
    }

    log.Printf("Attempting to update NetworkPolicy '%s' (ResourceVersion: %s)...\n", currentPolicy.Name, currentPolicy.ResourceVersion)
    _, updateErr := networkPoliciesClient.Update(context.TODO(), currentPolicy, metav1.UpdateOptions{})
	if updateErr == nil {
		log.Println("Update successful!")
	} else {
		log.Printf("Update failed: %v. Retrying...\n", updateErr)
	}
    return updateErr // Return error for RetryOnConflict
})

// Check final retry status
if retryErr != nil {
    // Handle specific "not found" error if needed
    if retryErr.Error() == "policy not found" {
         os.Exit(1) // Exit if policy wasn't found initially
    }
	log.Fatalf("Failed to update NetworkPolicy '%s' after retries: %s", policyName, retryErr.Error())
} else {
    log.Printf("NetworkPolicy '%s' update process complete.\n", policyName)
}

Explanation:

  • Retry Wrapper: We use retry.RetryOnConflict for robustness against concurrent modifications.

  • Get Latest: Inside the loop, we fetch the current policy version.

  • Modify: We construct the new NetworkPolicyIngressRule and append it to the currentPolicy.Spec.Ingress slice. We added checks to ensure Ingress is in PolicyTypes and to avoid adding duplicate rules (the duplicate check is basic here).

  • Conditional Update: A flag policyUpdated tracks if we actually made changes. The Update call only happens if modifications were necessary.

  • Update Call: networkPoliciesClient.Update submits the changed object. The error is returned to RetryOnConflict.

Deleting Network Policies

Removing a policy is straightforward using the Delete method:

policyName := "policy-to-delete"
namespace := "my-app"
networkPoliciesClient := clientset.NetworkingV1().NetworkPolicies(namespace)

log.Printf("Deleting NetworkPolicy '%s' in namespace '%s'...\n", policyName, namespace)
// Optional: Define deletion propagation policy
// deletePolicy := metav1.DeletePropagationForeground // Or Background/Orphan
// deleteOptions := metav1.DeleteOptions{
//     PropagationPolicy: &deletePolicy,
// }
// For simple deletion, empty DeleteOptions{} is often sufficient
deleteOptions := metav1.DeleteOptions{}

err := networkPoliciesClient.Delete(context.TODO(), policyName, deleteOptions)
if err != nil {
    if k8serrors.IsNotFound(err) {
        log.Printf("NetworkPolicy '%s' already deleted or not found.\n", policyName)
    } else {
        log.Fatalf("Error deleting NetworkPolicy '%s': %s\n", policyName, err.Error())
    }
} else {
    log.Printf("Successfully deleted NetworkPolicy '%s' (or deletion initiated).\n", policyName)
}

Programmatically managing Network Policies allows you to automate the enforcement of security rules, integrate security configurations into your deployment pipelines, or build tools that dynamically adjust network access based on application state or external triggers. Remember that the effectiveness of these policies depends entirely on having a CNI plugin that supports and enforces them.

Last updated

Was this helpful?