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: ¶ms, 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 }