github.com/cilium/cilium@v1.16.2/operator/pkg/networkpolicy/cell.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  // The networkpolicy package performs basic policy validation and updates
     5  // the policy's Status field as relevant.
     6  
     7  package networkpolicy
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  
    13  	"github.com/cilium/hive/cell"
    14  	"github.com/cilium/hive/job"
    15  	"github.com/sirupsen/logrus"
    16  	"github.com/spf13/pflag"
    17  	corev1 "k8s.io/api/core/v1"
    18  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  
    21  	cilium_api_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    22  	k8s_client "github.com/cilium/cilium/pkg/k8s/client"
    23  	"github.com/cilium/cilium/pkg/k8s/resource"
    24  	slimv1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    25  	"github.com/cilium/cilium/pkg/logging/logfields"
    26  	"github.com/cilium/cilium/pkg/option"
    27  )
    28  
    29  var Cell = cell.Module(
    30  	"network-policy-validator",
    31  	"Validates CNPs and CCNPs and reports their validity status",
    32  
    33  	cell.Config(defaultConfig),
    34  	cell.Invoke(registerPolicyValidator),
    35  )
    36  
    37  type Config struct {
    38  	ValidateNetworkPolicy bool `mapstructure:"validate-network-policy"`
    39  }
    40  
    41  var defaultConfig = Config{
    42  	ValidateNetworkPolicy: true,
    43  }
    44  
    45  func (def Config) Flags(flags *pflag.FlagSet) {
    46  	flags.Bool("validate-network-policy", def.ValidateNetworkPolicy, "Whether to enable or disable the informational network policy validator")
    47  }
    48  
    49  type PolicyParams struct {
    50  	cell.In
    51  
    52  	Logger       logrus.FieldLogger
    53  	JobGroup     job.Group
    54  	Clientset    k8s_client.Clientset
    55  	DaemonConfig *option.DaemonConfig
    56  
    57  	Cfg Config
    58  
    59  	CNPResource  resource.Resource[*cilium_api_v2.CiliumNetworkPolicy]
    60  	CCNPResource resource.Resource[*cilium_api_v2.CiliumClusterwideNetworkPolicy]
    61  }
    62  
    63  // The policyValidator validates network policy and reports the results in to the
    64  // policy's Status field. It validates both CiliumNetworkPolicy and CilumClusterwideNetworkPolicy
    65  type policyValidator struct {
    66  	params *PolicyParams
    67  }
    68  
    69  func registerPolicyValidator(params PolicyParams) {
    70  	if !params.Cfg.ValidateNetworkPolicy {
    71  		params.Logger.Debug("CNP / CCNP validator disabled")
    72  		return
    73  	}
    74  
    75  	pv := &policyValidator{
    76  		params: &params,
    77  	}
    78  
    79  	params.Logger.Info("Registering CNP / CCNP validator")
    80  	params.JobGroup.Add(job.Observer(
    81  		"cnp-validation",
    82  		pv.handleCNPEvent,
    83  		params.CNPResource,
    84  	))
    85  	params.JobGroup.Add(job.Observer(
    86  		"ccnp-validation",
    87  		pv.handleCCNPEvent,
    88  		params.CCNPResource,
    89  	))
    90  }
    91  
    92  func (pv *policyValidator) handleCNPEvent(ctx context.Context, event resource.Event[*cilium_api_v2.CiliumNetworkPolicy]) error {
    93  	var err error
    94  	defer func() {
    95  		event.Done(err)
    96  	}()
    97  	if event.Kind != resource.Upsert {
    98  		return nil
    99  	}
   100  
   101  	pol := event.Object
   102  	log := pv.params.Logger.WithFields(logrus.Fields{
   103  		logfields.K8sNamespace:            pol.Namespace,
   104  		logfields.CiliumNetworkPolicyName: pol.Name,
   105  	})
   106  
   107  	var errs error
   108  	if pol.Spec != nil {
   109  		errs = errors.Join(pol.Spec.Sanitize())
   110  	}
   111  	for _, r := range pol.Specs {
   112  		errs = errors.Join(r.Sanitize())
   113  	}
   114  
   115  	newPol := pol.DeepCopy()
   116  	newPol.Status.Conditions = updateCondition(event.Object.Status.Conditions, errs)
   117  	if newPol.Status.DeepEqual(&pol.Status) {
   118  		return nil
   119  	}
   120  
   121  	if errs != nil {
   122  		log.WithField(logfields.Error, errs).Debug("Detected invalid CNP, setting condition")
   123  	} else {
   124  		log.Debug("CNP now valid, setting condition")
   125  	}
   126  	// Using the UpdateStatus subresource will prevent the generation from being bumped.
   127  	_, err = pv.params.Clientset.CiliumV2().CiliumNetworkPolicies(pol.Namespace).UpdateStatus(
   128  		ctx,
   129  		newPol,
   130  		metav1.UpdateOptions{},
   131  	)
   132  	if err != nil {
   133  		if apierrors.IsNotFound(err) {
   134  			return nil
   135  		}
   136  		log.WithError(err).Error("failed to update CNP status")
   137  	}
   138  
   139  	return err
   140  }
   141  
   142  func (pv *policyValidator) handleCCNPEvent(ctx context.Context, event resource.Event[*cilium_api_v2.CiliumClusterwideNetworkPolicy]) error {
   143  	var err error
   144  	defer func() {
   145  		event.Done(err)
   146  	}()
   147  	if event.Kind != resource.Upsert {
   148  		return nil
   149  	}
   150  
   151  	pol := event.Object
   152  	log := pv.params.Logger.WithFields(logrus.Fields{
   153  		logfields.K8sNamespace:                       pol.Namespace,
   154  		logfields.CiliumClusterwideNetworkPolicyName: pol.Name,
   155  	})
   156  
   157  	var errs error
   158  	if pol.Spec != nil {
   159  		errs = errors.Join(pol.Spec.Sanitize())
   160  	}
   161  	for _, r := range pol.Specs {
   162  		errs = errors.Join(r.Sanitize())
   163  	}
   164  
   165  	newPol := pol.DeepCopy()
   166  	newPol.Status.Conditions = updateCondition(event.Object.Status.Conditions, errs)
   167  	if newPol.Status.DeepEqual(&pol.Status) {
   168  		return nil
   169  	}
   170  
   171  	if errs != nil {
   172  		log.WithField(logfields.Error, errs).Debug("Detected invalid CCNP, setting condition")
   173  	} else {
   174  		log.Debug("CCNP now valid, setting condition")
   175  	}
   176  	// Using the UpdateStatus subresource will prevent the generation from being bumped.
   177  	_, err = pv.params.Clientset.CiliumV2().CiliumClusterwideNetworkPolicies().UpdateStatus(
   178  		ctx,
   179  		newPol,
   180  		metav1.UpdateOptions{},
   181  	)
   182  	if err != nil {
   183  		if apierrors.IsNotFound(err) {
   184  			return nil
   185  		}
   186  		log.WithError(err).Error("failed to update CCNP status")
   187  	}
   188  
   189  	return err
   190  }
   191  
   192  // updateCondition creates or updates the policy validation condition in Conditions, setting
   193  // the transition time as necessary.
   194  func updateCondition(conditions []cilium_api_v2.NetworkPolicyCondition, errs error) []cilium_api_v2.NetworkPolicyCondition {
   195  	wantCondition := corev1.ConditionTrue
   196  	message := "Policy validation succeeded"
   197  	if errs != nil {
   198  		wantCondition = corev1.ConditionFalse
   199  		message = errs.Error()
   200  	}
   201  
   202  	// look for the condition type already existing.
   203  	foundIdx := -1
   204  	for i, cond := range conditions {
   205  		if cond.Type == cilium_api_v2.PolicyConditionValid {
   206  			foundIdx = i
   207  			// If nothing important changed, short-circuit
   208  			if cond.Status == wantCondition && cond.Message == message {
   209  				return conditions
   210  			}
   211  			break
   212  		}
   213  	}
   214  
   215  	// Otherwise, set / update the condition
   216  	newCond := cilium_api_v2.NetworkPolicyCondition{
   217  		Type:               cilium_api_v2.PolicyConditionValid,
   218  		Status:             wantCondition,
   219  		LastTransitionTime: slimv1.Now(),
   220  		Message:            message,
   221  	}
   222  
   223  	out := append([]cilium_api_v2.NetworkPolicyCondition{}, conditions...)
   224  
   225  	if foundIdx >= 0 {
   226  		// If the status did not change (just the message), don't bump the
   227  		// LastTransitionTime.
   228  		if out[foundIdx].Status == newCond.Status {
   229  			newCond.LastTransitionTime = out[foundIdx].LastTransitionTime
   230  		}
   231  		out[foundIdx] = newCond
   232  	} else {
   233  		out = append(out, newCond)
   234  	}
   235  	return out
   236  }