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:
networkPoliciesClient := clientset.NetworkingV1().NetworkPolicies(namespace)
Define the Policy: Construct the
*networkingv1.NetworkPolicy
object in Go, carefully definingmetadata.name
,metadata.namespace
,spec.podSelector
,spec.policyTypes
, and thespec.ingress
and/orspec.egress
rules. Use helpers likemetav1.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.
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
.
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
orspec.egress
slice.Modify an existing rule's
from
,to
, orports
.Change the
spec.podSelector
.Update
spec.policyTypes
.
Update: Call
networkPoliciesClient.Update(...)
with the modified object. Useretry.RetryOnConflict
to automatically handleIsConflict
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 thecurrentPolicy.Spec.Ingress
slice. We added checks to ensureIngress
is inPolicyTypes
and to avoid adding duplicate rules (the duplicate check is basic here).Conditional Update: A flag
policyUpdated
tracks if we actually made changes. TheUpdate
call only happens if modifications were necessary.Update Call:
networkPoliciesClient.Update
submits the changed object. The error is returned toRetryOnConflict
.
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?