github.com/cilium/cilium@v1.16.2/pkg/policy/groups/helpers.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package groups
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  
    12  	cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    13  	"github.com/cilium/cilium/pkg/k8s/client"
    14  	slimv1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    15  	"github.com/cilium/cilium/pkg/policy/api"
    16  )
    17  
    18  const (
    19  	cnpKindName = "derivative"
    20  	parentCNP   = "io.cilium.network.policy.parent.uuid"
    21  	cnpKindKey  = "io.cilium.network.policy.kind"
    22  )
    23  
    24  func getDerivativeName(obj v1.Object) string {
    25  	return obj.GetName() + "-groups-" + string(obj.GetUID())
    26  }
    27  
    28  // createDerivativeCNP will return a new CNP based on the given rule.
    29  func createDerivativeCNP(ctx context.Context, cnp *cilium_v2.CiliumNetworkPolicy) (*cilium_v2.CiliumNetworkPolicy, error) {
    30  	// CNP informer may provide a CNP object without APIVersion or Kind.
    31  	// Setting manually to make sure that the derivative policy works ok.
    32  	derivativeCNP := &cilium_v2.CiliumNetworkPolicy{
    33  		ObjectMeta: v1.ObjectMeta{
    34  			Name:      getDerivativeName(cnp),
    35  			Namespace: cnp.ObjectMeta.Namespace,
    36  			OwnerReferences: []v1.OwnerReference{{
    37  				APIVersion: cilium_v2.SchemeGroupVersion.String(),
    38  				Kind:       cilium_v2.CNPKindDefinition,
    39  				Name:       cnp.ObjectMeta.Name,
    40  				UID:        cnp.ObjectMeta.UID,
    41  			}},
    42  			Labels: map[string]string{
    43  				parentCNP:  string(cnp.ObjectMeta.UID),
    44  				cnpKindKey: cnpKindName,
    45  			},
    46  		},
    47  	}
    48  
    49  	var (
    50  		rules api.Rules
    51  		err   error
    52  	)
    53  
    54  	rules, err = cnp.Parse()
    55  
    56  	if err != nil {
    57  		// We return a valid pointer for derivative policy here instead of nil.
    58  		// This object is used to get generated name for the derivative policy
    59  		// when updating the status of the network policy.
    60  		return derivativeCNP, fmt.Errorf("cannot parse CNP: %w", err)
    61  	}
    62  
    63  	derivativeCNP.Specs, err = createAPIRules(ctx, rules)
    64  
    65  	return derivativeCNP, err
    66  }
    67  
    68  // createDerivativeCCNP will return a new CCNP based on the given rule.
    69  func createDerivativeCCNP(ctx context.Context, cnp *cilium_v2.CiliumNetworkPolicy) (*cilium_v2.CiliumClusterwideNetworkPolicy, error) {
    70  	ccnp := &cilium_v2.CiliumClusterwideNetworkPolicy{
    71  		TypeMeta:   cnp.TypeMeta,
    72  		ObjectMeta: cnp.ObjectMeta,
    73  		Spec:       cnp.Spec,
    74  		Specs:      cnp.Specs,
    75  		Status:     cnp.Status,
    76  	}
    77  
    78  	// CCNP informer may provide a CCNP object without APIVersion or Kind.
    79  	// Setting manually to make sure that the derivative policy works ok.
    80  	derivativeCCNP := &cilium_v2.CiliumClusterwideNetworkPolicy{
    81  		ObjectMeta: v1.ObjectMeta{
    82  			Name:      getDerivativeName(ccnp),
    83  			Namespace: ccnp.ObjectMeta.Namespace,
    84  			OwnerReferences: []v1.OwnerReference{{
    85  				APIVersion: cilium_v2.SchemeGroupVersion.String(),
    86  				Kind:       cilium_v2.CCNPKindDefinition,
    87  				Name:       ccnp.ObjectMeta.Name,
    88  				UID:        ccnp.ObjectMeta.UID,
    89  			}},
    90  			Labels: map[string]string{
    91  				parentCNP:  string(ccnp.ObjectMeta.UID),
    92  				cnpKindKey: cnpKindName,
    93  			},
    94  		},
    95  	}
    96  
    97  	var (
    98  		rules api.Rules
    99  		err   error
   100  	)
   101  
   102  	rules, err = ccnp.Parse()
   103  
   104  	if err != nil {
   105  		// We return a valid pointer for derivative policy here instead of nil.
   106  		// This object is used to get generated name for the derivative policy
   107  		// when updating the status of the network policy.
   108  		return derivativeCCNP, fmt.Errorf("cannot parse CCNP: %w", err)
   109  	}
   110  
   111  	derivativeCCNP.Specs, err = createAPIRules(ctx, rules)
   112  
   113  	return derivativeCCNP, err
   114  }
   115  
   116  func createAPIRules(ctx context.Context, rules api.Rules) (api.Rules, error) {
   117  	specRules := make(api.Rules, len(rules))
   118  	for i, rule := range rules {
   119  		if rule.RequiresDerivative() {
   120  			specRules[i] = denyEgressRule()
   121  		}
   122  	}
   123  
   124  	for i, rule := range rules {
   125  		if !rule.RequiresDerivative() {
   126  			specRules[i] = rule
   127  			continue
   128  		}
   129  		newRule, err := rule.CreateDerivative(ctx)
   130  		if err != nil {
   131  			return specRules, err
   132  		}
   133  		specRules[i] = newRule
   134  	}
   135  	return specRules, nil
   136  }
   137  
   138  func denyEgressRule() *api.Rule {
   139  	return &api.Rule{
   140  		Egress: []api.EgressRule{},
   141  	}
   142  }
   143  
   144  func updateOrCreateCNP(clientset client.Clientset, cnp *cilium_v2.CiliumNetworkPolicy) (*cilium_v2.CiliumNetworkPolicy, error) {
   145  	k8sCNP, err := clientset.CiliumV2().CiliumNetworkPolicies(cnp.ObjectMeta.Namespace).
   146  		Get(context.TODO(), cnp.ObjectMeta.Name, v1.GetOptions{})
   147  	if err == nil {
   148  		k8sCNP.ObjectMeta.Labels = cnp.ObjectMeta.Labels
   149  		k8sCNP.Spec = cnp.Spec
   150  		k8sCNP.Specs = cnp.Specs
   151  		k8sCNP.Status = cilium_v2.CiliumNetworkPolicyStatus{}
   152  		return clientset.CiliumV2().CiliumNetworkPolicies(cnp.ObjectMeta.Namespace).Update(context.TODO(), k8sCNP, v1.UpdateOptions{})
   153  	}
   154  	return clientset.CiliumV2().CiliumNetworkPolicies(cnp.ObjectMeta.Namespace).Create(context.TODO(), cnp, v1.CreateOptions{})
   155  }
   156  
   157  func updateOrCreateCCNP(clientset client.Clientset, ccnp *cilium_v2.CiliumClusterwideNetworkPolicy) (*cilium_v2.CiliumClusterwideNetworkPolicy, error) {
   158  	k8sCCNP, err := clientset.CiliumV2().CiliumClusterwideNetworkPolicies().
   159  		Get(context.TODO(), ccnp.ObjectMeta.Name, v1.GetOptions{})
   160  	if err == nil {
   161  		k8sCCNP.ObjectMeta.Labels = ccnp.ObjectMeta.Labels
   162  		k8sCCNP.Spec = ccnp.Spec
   163  		k8sCCNP.Specs = ccnp.Specs
   164  		k8sCCNP.Status = cilium_v2.CiliumNetworkPolicyStatus{}
   165  
   166  		return clientset.CiliumV2().CiliumClusterwideNetworkPolicies().Update(context.TODO(), k8sCCNP, v1.UpdateOptions{})
   167  	}
   168  
   169  	return clientset.CiliumV2().CiliumClusterwideNetworkPolicies().
   170  		Create(context.TODO(), ccnp, v1.CreateOptions{})
   171  }
   172  
   173  func updateDerivativeStatus(clientset client.Clientset, cnp *cilium_v2.CiliumNetworkPolicy, derivativeName string, err error, clusterScoped bool) error {
   174  	status := cilium_v2.CiliumNetworkPolicyNodeStatus{
   175  		LastUpdated: slimv1.Now(),
   176  		Enforcing:   false,
   177  	}
   178  
   179  	if err != nil {
   180  		status.OK = false
   181  		status.Error = err.Error()
   182  	} else {
   183  		status.OK = true
   184  	}
   185  
   186  	if clusterScoped {
   187  		return updateDerivativeCCNPStatus(clientset, cnp, status, derivativeName)
   188  	}
   189  
   190  	return updateDerivativeCNPStatus(clientset, cnp, status, derivativeName)
   191  }
   192  
   193  func updateDerivativeCNPStatus(clientset client.Clientset, cnp *cilium_v2.CiliumNetworkPolicy, status cilium_v2.CiliumNetworkPolicyNodeStatus,
   194  	derivativeName string) error {
   195  	// This CNP can be modified by cilium agent or operator. To be able to push
   196  	// the status correctly fetch the last version to avoid updates issues.
   197  	k8sCNP, clientErr := clientset.CiliumV2().CiliumNetworkPolicies(cnp.ObjectMeta.Namespace).
   198  		Get(context.TODO(), cnp.ObjectMeta.Name, v1.GetOptions{})
   199  
   200  	if clientErr != nil {
   201  		return fmt.Errorf("cannot get Kubernetes policy: %w", clientErr)
   202  	}
   203  
   204  	if k8sCNP.ObjectMeta.UID != cnp.ObjectMeta.UID {
   205  		// This case should not happen, but if the UID does not match make sure
   206  		// that the new policy is not in the cache to not loop over it. The
   207  		// kubernetes watcher should take care about that.
   208  		groupsCNPCache.DeleteCNP(k8sCNP)
   209  		return fmt.Errorf("policy UID mismatch")
   210  	}
   211  
   212  	k8sCNP.SetDerivedPolicyStatus(derivativeName, status)
   213  	groupsCNPCache.UpdateCNP(k8sCNP)
   214  
   215  	// TODO: Switch to JSON patch.
   216  	_, err := clientset.CiliumV2().CiliumNetworkPolicies(cnp.ObjectMeta.Namespace).
   217  		UpdateStatus(context.TODO(), k8sCNP, v1.UpdateOptions{})
   218  
   219  	return err
   220  }
   221  
   222  func updateDerivativeCCNPStatus(clientset client.Clientset, cnp *cilium_v2.CiliumNetworkPolicy, status cilium_v2.CiliumNetworkPolicyNodeStatus,
   223  	derivativeName string) error {
   224  	k8sCCNP, clientErr := clientset.CiliumV2().CiliumClusterwideNetworkPolicies().
   225  		Get(context.TODO(), cnp.ObjectMeta.Name, v1.GetOptions{})
   226  
   227  	if clientErr != nil {
   228  		return fmt.Errorf("cannot get Kubernetes policy: %w", clientErr)
   229  	}
   230  
   231  	if k8sCCNP.ObjectMeta.UID != cnp.ObjectMeta.UID {
   232  		// This case should not happen, but if the UID does not match make sure
   233  		// that the new policy is not in the cache to not loop over it. The
   234  		// kubernetes watcher should take care of that.
   235  		groupsCNPCache.DeleteCNP(&cilium_v2.CiliumNetworkPolicy{
   236  			ObjectMeta: k8sCCNP.ObjectMeta,
   237  		})
   238  		return fmt.Errorf("policy UID mismatch")
   239  	}
   240  
   241  	k8sCCNP.SetDerivedPolicyStatus(derivativeName, status)
   242  	groupsCNPCache.UpdateCNP(&cilium_v2.CiliumNetworkPolicy{
   243  		TypeMeta:   k8sCCNP.TypeMeta,
   244  		ObjectMeta: k8sCCNP.ObjectMeta,
   245  		Spec:       k8sCCNP.Spec,
   246  		Specs:      k8sCCNP.Specs,
   247  		Status:     k8sCCNP.Status,
   248  	})
   249  
   250  	// TODO: Switch to JSON patch
   251  	_, err := clientset.CiliumV2().CiliumClusterwideNetworkPolicies().
   252  		UpdateStatus(context.TODO(), k8sCCNP, v1.UpdateOptions{})
   253  
   254  	return err
   255  
   256  }