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.
Get the Client: Obtain the NetworkPolicy client for the target namespace:
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 },}
Call Create: Use the client to create the resource in the cluster.
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.
Get: Retrieve the current NetworkPolicy using networkPoliciesClient.Get(...).
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.
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:
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.