diff --git a/api/v1/clusternimbuspolicy_types.go b/api/v1/clusternimbuspolicy_types.go index e8861575..841e0f5b 100644 --- a/api/v1/clusternimbuspolicy_types.go +++ b/api/v1/clusternimbuspolicy_types.go @@ -15,13 +15,18 @@ type ClusterNimbusPolicySpec struct { // ClusterNimbusPolicyStatus defines the observed state of ClusterNimbusPolicy type ClusterNimbusPolicyStatus struct { - Status string `json:"status"` + Status string `json:"status"` + LastUpdated metav1.Time `json:"lastUpdated,omitempty"` + NumberOfAdapterPolicies int32 `json:"numberOfAdapterPolicies"` + Policies []string `json:"adapterPolicies,omitempty"` } //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:resource:scope=Cluster,shortName="cwnp" //+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status" +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +//+kubebuilder:printcolumn:name="Policies",type="integer",JSONPath=".status.numberOfAdapterPolicies" // ClusterNimbusPolicy is the Schema for the clusternimbuspolicies API type ClusterNimbusPolicy struct { diff --git a/api/v1/clustersecurityintentbinding_types.go b/api/v1/clustersecurityintentbinding_types.go index 4ca68b49..d400b5a8 100644 --- a/api/v1/clustersecurityintentbinding_types.go +++ b/api/v1/clustersecurityintentbinding_types.go @@ -27,13 +27,20 @@ type ClusterSecurityIntentBindingSpec struct { // ClusterSecurityIntentBindingStatus defines the observed state of ClusterSecurityIntentBinding type ClusterSecurityIntentBindingStatus struct { - Status string `json:"status"` + Status string `json:"status"` + LastUpdated metav1.Time `json:"lastUpdated,omitempty"` + NumberOfBoundIntents int32 `json:"numberOfBoundIntents"` + BoundIntents []string `json:"boundIntents,omitempty"` + ClusterNimbusPolicy string `json:"clusterNimbusPolicy"` } //+kubebuilder:object:root=true //+kubebuilder:subresource:status -//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status" //+kubebuilder:resource:scope=Cluster,shortName="csib" +//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status" +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +//+kubebuilder:printcolumn:name="Intents",type="integer",JSONPath=".status.numberOfBoundIntents" +//+kubebuilder:printcolumn:name="ClusterNimbusPolicy",type="string",JSONPath=".status.clusterNimbusPolicy" //+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ClusterSecurityIntentBinding is the Schema for the clustersecurityintentbindings API diff --git a/api/v1/nimbuspolicy_types.go b/api/v1/nimbuspolicy_types.go index 2ab694b0..5e17223f 100644 --- a/api/v1/nimbuspolicy_types.go +++ b/api/v1/nimbuspolicy_types.go @@ -32,20 +32,23 @@ type NimbusRules struct { type Rule struct { RuleAction string `json:"action"` - Mode string `json:"mode"` Params map[string][]string `json:"params,omitempty"` } // NimbusPolicyStatus defines the observed state of NimbusPolicy type NimbusPolicyStatus struct { - Status string `json:"status"` - LastUpdated metav1.Time `json:"lastUpdated,omitempty"` + Status string `json:"status"` + LastUpdated metav1.Time `json:"lastUpdated,omitempty"` + NumberOfAdapterPolicies int32 `json:"numberOfAdapterPolicies"` + Policies []string `json:"adapterPolicies,omitempty"` } //+kubebuilder:object:root=true //+kubebuilder:subresource:status -//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status" //+kubebuilder:resource: shortName="np" +//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status" +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +//+kubebuilder:printcolumn:name="Policies",type="integer",JSONPath=".status.numberOfAdapterPolicies" // NimbusPolicy is the Schema for the nimbuspolicies API type NimbusPolicy struct { diff --git a/api/v1/securityintent_types.go b/api/v1/securityintent_types.go index e3774050..d1b3208a 100644 --- a/api/v1/securityintent_types.go +++ b/api/v1/securityintent_types.go @@ -25,11 +25,6 @@ type Intent struct { // Action defines how the security policy will be enforced. Action string `json:"action"` - // Mode defines the enforcement behavior of the intent. - // Defaults to best-effort. - //+kubebuilder:default:="best-effort" - Mode string `json:"mode,omitempty"` - // Severity defines the potential impact of a security violation related to the intent. // Defaults to Low. //+kubebuilder:default:=Low @@ -45,14 +40,18 @@ type Intent struct { // SecurityIntentStatus defines the observed state of SecurityIntent type SecurityIntentStatus struct { + ID string `json:"id"` + Action string `json:"action"` Status string `json:"status"` } -// SecurityIntent is the Schema for the securityintents API // +kubebuilder:object:root=true // +kubebuilder:resource:shortName="si",scope="Cluster" // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="ID",type="string",JSONPath=".spec.intent.id",priority=1 +// +kubebuilder:printcolumn:name="Action",type="string",JSONPath=".spec.intent.action",priority=1 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // SecurityIntent is the Schema for the securityintents API diff --git a/api/v1/securityintentbinding_types.go b/api/v1/securityintentbinding_types.go index 41006c3c..9ba9192f 100644 --- a/api/v1/securityintentbinding_types.go +++ b/api/v1/securityintentbinding_types.go @@ -39,14 +39,20 @@ type Resources struct { // SecurityIntentBindingStatus defines the observed state of SecurityIntentBinding type SecurityIntentBindingStatus struct { - Status string `json:"status"` - LastUpdated metav1.Time `json:"lastUpdated,omitempty"` + Status string `json:"status"` + LastUpdated metav1.Time `json:"lastUpdated,omitempty"` + NumberOfBoundIntents int32 `json:"numberOfBoundIntents"` + BoundIntents []string `json:"boundIntents,omitempty"` + NimbusPolicy string `json:"nimbusPolicy"` } // +kubebuilder:object:root=true // +kubebuilder:resource: shortName="sib" // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:printcolumn:name="Intents",type="integer",JSONPath=".status.numberOfBoundIntents" +// +kubebuilder:printcolumn:name="NimbusPolicy",type="string",JSONPath=".status.nimbusPolicy" // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // SecurityIntentBinding is the Schema for the securityintentbindings API diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 69b39925..59b56c06 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -17,7 +17,7 @@ func (in *ClusterNimbusPolicy) DeepCopyInto(out *ClusterNimbusPolicy) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterNimbusPolicy. @@ -96,6 +96,12 @@ func (in *ClusterNimbusPolicySpec) DeepCopy() *ClusterNimbusPolicySpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterNimbusPolicyStatus) DeepCopyInto(out *ClusterNimbusPolicyStatus) { *out = *in + in.LastUpdated.DeepCopyInto(&out.LastUpdated) + if in.Policies != nil { + in, out := &in.Policies, &out.Policies + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterNimbusPolicyStatus. @@ -114,7 +120,7 @@ func (in *ClusterSecurityIntentBinding) DeepCopyInto(out *ClusterSecurityIntentB out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSecurityIntentBinding. @@ -191,6 +197,12 @@ func (in *ClusterSecurityIntentBindingSpec) DeepCopy() *ClusterSecurityIntentBin // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterSecurityIntentBindingStatus) DeepCopyInto(out *ClusterSecurityIntentBindingStatus) { *out = *in + in.LastUpdated.DeepCopyInto(&out.LastUpdated) + if in.BoundIntents != nil { + in, out := &in.BoundIntents, &out.BoundIntents + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSecurityIntentBindingStatus. @@ -389,6 +401,11 @@ func (in *NimbusPolicySpec) DeepCopy() *NimbusPolicySpec { func (in *NimbusPolicyStatus) DeepCopyInto(out *NimbusPolicyStatus) { *out = *in in.LastUpdated.DeepCopyInto(&out.LastUpdated) + if in.Policies != nil { + in, out := &in.Policies, &out.Policies + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NimbusPolicyStatus. @@ -619,6 +636,11 @@ func (in *SecurityIntentBindingSpec) DeepCopy() *SecurityIntentBindingSpec { func (in *SecurityIntentBindingStatus) DeepCopyInto(out *SecurityIntentBindingStatus) { *out = *in in.LastUpdated.DeepCopyInto(&out.LastUpdated) + if in.BoundIntents != nil { + in, out := &in.BoundIntents, &out.BoundIntents + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityIntentBindingStatus. diff --git a/cmd/main.go b/cmd/main.go index cad84d34..b947f3a1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -7,14 +7,11 @@ import ( "flag" "os" - // Importing all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) - // to ensure that exec-entrypoint and run can utilize them. - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/config" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" @@ -43,6 +40,7 @@ func main() { var metricsAddr string var enableLeaderElection bool var probeAddr string + recoverPanic := true flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") @@ -59,8 +57,11 @@ func main() { Scheme: scheme, Metrics: metricsserver.Options{BindAddress: metricsAddr}, HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, - LeaderElectionID: "44502a2e.security.nimbus.com", + Controller: config.Controller{ + RecoverPanic: &recoverPanic, + }, + LeaderElection: enableLeaderElection, + LeaderElectionID: "44502a2e.security.nimbus.com", // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily // when the Manager ends. This requires the binary to immediately end when the // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly diff --git a/config/crd/bases/intent.security.nimbus.com_clusternimbuspolicies.yaml b/config/crd/bases/intent.security.nimbus.com_clusternimbuspolicies.yaml index b623d976..3b4acfd8 100644 --- a/config/crd/bases/intent.security.nimbus.com_clusternimbuspolicies.yaml +++ b/config/crd/bases/intent.security.nimbus.com_clusternimbuspolicies.yaml @@ -20,6 +20,12 @@ spec: - jsonPath: .status.status name: Status type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.numberOfAdapterPolicies + name: Policies + type: integer name: v1 schema: openAPIV3Schema: @@ -54,8 +60,6 @@ spec: properties: action: type: string - mode: - type: string params: additionalProperties: items: @@ -64,7 +68,6 @@ spec: type: object required: - action - - mode type: object type: type: string @@ -105,9 +108,20 @@ spec: status: description: ClusterNimbusPolicyStatus defines the observed state of ClusterNimbusPolicy properties: + adapterPolicies: + items: + type: string + type: array + lastUpdated: + format: date-time + type: string + numberOfAdapterPolicies: + format: int32 + type: integer status: type: string required: + - numberOfAdapterPolicies - status type: object type: object diff --git a/config/crd/bases/intent.security.nimbus.com_clustersecurityintentbindings.yaml b/config/crd/bases/intent.security.nimbus.com_clustersecurityintentbindings.yaml index 6261f5b4..9be50f48 100644 --- a/config/crd/bases/intent.security.nimbus.com_clustersecurityintentbindings.yaml +++ b/config/crd/bases/intent.security.nimbus.com_clustersecurityintentbindings.yaml @@ -20,6 +20,15 @@ spec: - jsonPath: .status.status name: Status type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.numberOfBoundIntents + name: Intents + type: integer + - jsonPath: .status.clusterNimbusPolicy + name: ClusterNimbusPolicy + type: string name: v1 schema: openAPIV3Schema: @@ -86,9 +95,23 @@ spec: description: ClusterSecurityIntentBindingStatus defines the observed state of ClusterSecurityIntentBinding properties: + boundIntents: + items: + type: string + type: array + clusterNimbusPolicy: + type: string + lastUpdated: + format: date-time + type: string + numberOfBoundIntents: + format: int32 + type: integer status: type: string required: + - clusterNimbusPolicy + - numberOfBoundIntents - status type: object type: object diff --git a/config/crd/bases/intent.security.nimbus.com_nimbuspolicies.yaml b/config/crd/bases/intent.security.nimbus.com_nimbuspolicies.yaml index ebf00556..d4d541ea 100644 --- a/config/crd/bases/intent.security.nimbus.com_nimbuspolicies.yaml +++ b/config/crd/bases/intent.security.nimbus.com_nimbuspolicies.yaml @@ -20,6 +20,12 @@ spec: - jsonPath: .status.status name: Status type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.numberOfAdapterPolicies + name: Policies + type: integer name: v1 schema: openAPIV3Schema: @@ -55,8 +61,6 @@ spec: properties: action: type: string - mode: - type: string params: additionalProperties: items: @@ -65,7 +69,6 @@ spec: type: object required: - action - - mode type: object type: type: string @@ -94,12 +97,20 @@ spec: status: description: NimbusPolicyStatus defines the observed state of NimbusPolicy properties: + adapterPolicies: + items: + type: string + type: array lastUpdated: format: date-time type: string + numberOfAdapterPolicies: + format: int32 + type: integer status: type: string required: + - numberOfAdapterPolicies - status type: object type: object diff --git a/config/crd/bases/intent.security.nimbus.com_securityintentbindings.yaml b/config/crd/bases/intent.security.nimbus.com_securityintentbindings.yaml index d5e6d568..9e82ef5e 100644 --- a/config/crd/bases/intent.security.nimbus.com_securityintentbindings.yaml +++ b/config/crd/bases/intent.security.nimbus.com_securityintentbindings.yaml @@ -20,6 +20,15 @@ spec: - jsonPath: .status.status name: Status type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.numberOfBoundIntents + name: Intents + type: integer + - jsonPath: .status.nimbusPolicy + name: NimbusPolicy + type: string name: v1 schema: openAPIV3Schema: @@ -106,12 +115,23 @@ spec: description: SecurityIntentBindingStatus defines the observed state of SecurityIntentBinding properties: + boundIntents: + items: + type: string + type: array lastUpdated: format: date-time type: string + nimbusPolicy: + type: string + numberOfBoundIntents: + format: int32 + type: integer status: type: string required: + - nimbusPolicy + - numberOfBoundIntents - status type: object type: object diff --git a/config/crd/bases/intent.security.nimbus.com_securityintents.yaml b/config/crd/bases/intent.security.nimbus.com_securityintents.yaml index acb54792..5af260ea 100644 --- a/config/crd/bases/intent.security.nimbus.com_securityintents.yaml +++ b/config/crd/bases/intent.security.nimbus.com_securityintents.yaml @@ -20,6 +20,17 @@ spec: - jsonPath: .status.status name: Status type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.intent.id + name: ID + priority: 1 + type: string + - jsonPath: .spec.intent.action + name: Action + priority: 1 + type: string name: v1 schema: openAPIV3Schema: @@ -55,11 +66,6 @@ spec: engines to generate corresponding security policies. pattern: ^[a-zA-Z0-9]*$ type: string - mode: - default: best-effort - description: Mode defines the enforcement behavior of the intent. - Defaults to best-effort. - type: string params: additionalProperties: items: @@ -90,9 +96,15 @@ spec: status: description: SecurityIntentStatus defines the observed state of SecurityIntent properties: + action: + type: string + id: + type: string status: type: string required: + - action + - id - status type: object type: object diff --git a/go.mod b/go.mod index 32a5513a..22536c27 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/5GSEC/nimbus go 1.21 require ( + github.com/go-logr/logr v1.4.1 github.com/onsi/ginkgo/v2 v2.14.0 github.com/onsi/gomega v1.30.0 k8s.io/apimachinery v0.29.1 @@ -11,11 +12,8 @@ require ( ) require ( - github.com/5GSEC/nimbus/pkg/adapter/nimbus-kubearmor v0.0.0-20240208144202-ef6c819f09b3 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect - github.com/go-logr/logr v1.4.1 // indirect - github.com/kubearmor/KubeArmor/pkg/KubeArmorController v0.0.0-20240125171707-8e6641511fe3 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect @@ -71,7 +69,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.29.1 // indirect + k8s.io/api v0.29.1 k8s.io/apiextensions-apiserver v0.29.1 // indirect k8s.io/component-base v0.29.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect diff --git a/go.sum b/go.sum index 964f02e3..2bc9d759 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/5GSEC/nimbus/pkg/adapter/nimbus-kubearmor v0.0.0-20240208144202-ef6c819f09b3 h1:dcJ9kZy7Kn/uh36QxLdTLn0L0UtG744uFAJHwCDUDdU= -github.com/5GSEC/nimbus/pkg/adapter/nimbus-kubearmor v0.0.0-20240208144202-ef6c819f09b3/go.mod h1:TTZsB7iLwzcTzW9CjkqIUpvx8ShiQM7bSNDH7mYnV8w= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -68,8 +66,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kubearmor/KubeArmor/pkg/KubeArmorController v0.0.0-20240125171707-8e6641511fe3 h1:xDg2EAk7rV3psrUkwC7JqY6pzOutWwh4VuUEybypcrA= -github.com/kubearmor/KubeArmor/pkg/KubeArmorController v0.0.0-20240125171707-8e6641511fe3/go.mod h1:Z7ZPkMwtVcjSaDigSekvooXRxapWsLAVVmX3ltL673k= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/internal/controller/clustersecurityintentbinding_controller.go b/internal/controller/clustersecurityintentbinding_controller.go index 8edb35cd..dc7cddc9 100644 --- a/internal/controller/clustersecurityintentbinding_controller.go +++ b/internal/controller/clustersecurityintentbinding_controller.go @@ -6,15 +6,19 @@ package controller import ( "context" - "k8s.io/apimachinery/pkg/api/errors" + "github.com/go-logr/logr" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" v1 "github.com/5GSEC/nimbus/api/v1" - "github.com/5GSEC/nimbus/pkg/processor/intentbinder" "github.com/5GSEC/nimbus/pkg/processor/policybuilder" ) @@ -35,71 +39,187 @@ func (r *ClusterSecurityIntentBindingReconciler) Reconcile(ctx context.Context, logger := log.FromContext(ctx) csib := &v1.ClusterSecurityIntentBinding{} - err := r.Get(ctx, types.NamespacedName{Name: req.Name}, csib) + err := r.Get(ctx, req.NamespacedName, csib) if err != nil { - if errors.IsNotFound(err) { + if apierrors.IsNotFound(err) { logger.Info("ClusterSecurityIntentBinding not found. Ignoring since object must be deleted") - return ctrl.Result{}, nil + logger.Info("ClusterNimbusPolicy deleted due to ClusterSecurityIntentBinding deletion", + "ClusterNimbusPolicy.Name", req.Name, "ClusterSecurityIntentBinding.Name", req.Name, + ) + return doNotRequeue() } logger.Error(err, "failed to get ClusterSecurityIntentBinding", "ClusterSecurityIntentBinding.Name", csib.Name) - return ctrl.Result{}, err + return requeueWithError(err) } + logger.Info("ClusterSecurityIntentBinding found", "ClusterSecurityIntentBinding.Name", req.Name) - if csib.Status.Status == "" || csib.Status.Status == StatusPending { - csib.Status.Status = StatusCreated - if err := r.Status().Update(ctx, csib); err != nil { - logger.Error(err, "failed to update ClusterSecurityIntentBinding status", "ClusterSecurityIntentBinding.Name", csib.Name) - return ctrl.Result{}, err - } - // Let's re-fetch the ClusterSecurityIntentBinding CR after updating the status - // so that we have the latest state of the resource on the cluster. - if err := r.Get(ctx, req.NamespacedName, csib); err != nil { - logger.Error(err, "failed to re-fetch ClusterSecurityIntentBinding", "ClusterSecurityIntentBinding.Name", csib.Name) - return ctrl.Result{}, err - } - logger.Info("ClusterSecurityIntentBinding found", "ClusterSecurityIntentBinding.Name", csib.Name) - return ctrl.Result{}, nil + if err = r.createOrUpdateCwnp(ctx, logger, req); err != nil { + return requeueWithError(err) + } + + if err = r.updateStatus(ctx, logger, req); err != nil { + return requeueWithError(err) + } + + return doNotRequeue() +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ClusterSecurityIntentBindingReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1.ClusterSecurityIntentBinding{}). + Owns(&v1.ClusterNimbusPolicy{}). + WithEventFilter( + predicate.Funcs{ + CreateFunc: r.createFn, + UpdateFunc: r.updateFn, + DeleteFunc: r.deleteFn, + }, + ). + Complete(r) +} + +func (r *ClusterSecurityIntentBindingReconciler) createFn(createEvent event.CreateEvent) bool { + if _, ok := createEvent.Object.(*v1.ClusterNimbusPolicy); ok { + return false + } + return true +} + +func (r *ClusterSecurityIntentBindingReconciler) updateFn(updateEvent event.UpdateEvent) bool { + // TODO: Handle update event for ClusterNimbusPolicy update so that reconciler don't process it + // twice. + return updateEvent.ObjectOld.GetGeneration() != updateEvent.ObjectNew.GetGeneration() +} + +func (r *ClusterSecurityIntentBindingReconciler) deleteFn(deleteEvent event.DeleteEvent) bool { + obj := deleteEvent.Object + if _, ok := obj.(*v1.ClusterSecurityIntentBinding); ok { + return true + } + return ownerExists(r.Client, obj) +} + +func (r *ClusterSecurityIntentBindingReconciler) createOrUpdateCwnp(ctx context.Context, logger logr.Logger, req ctrl.Request) error { + // Always fetch the latest CRs so that we have the latest state of the CRs on the + // cluster. + var csib v1.ClusterSecurityIntentBinding + if err := r.Get(ctx, req.NamespacedName, &csib); err != nil { + logger.Error(err, "failed to fetch ClusterSecurityIntentBinding", "ClusterSecurityIntentBinding.Name", req.Name) + return err } - bindingInfo := intentbinder.MatchAndBindIntentsGlobal(ctx, r.Client, csib) + var cwnp v1.ClusterNimbusPolicy + err := r.Get(ctx, req.NamespacedName, &cwnp) + if err != nil { + if apierrors.IsNotFound(err) { + return r.createCwnp(ctx, logger, csib) + } + logger.Error(err, "failed to fetch ClusterNimbusPolicy", "ClusterNimbusPolicy.Name", req.Name) + return err + } + return r.updateCwnp(ctx, logger, csib) +} - clusterNp, err := policybuilder.BuildClusterNimbusPolicy(ctx, r.Client, r.Scheme, bindingInfo) +func (r *ClusterSecurityIntentBindingReconciler) createCwnp(ctx context.Context, logger logr.Logger, csib v1.ClusterSecurityIntentBinding) error { + clusterNp, err := policybuilder.BuildClusterNimbusPolicy(ctx, logger, r.Client, r.Scheme, csib) if err != nil { logger.Error(err, "failed to build ClusterNimbusPolicy") - return ctrl.Result{}, err + return err } - existingClusterNp := &v1.ClusterNimbusPolicy{} - err = r.Get(ctx, types.NamespacedName{Name: req.Name}, existingClusterNp) - if err != nil && errors.IsNotFound(err) { - if err := r.Create(ctx, clusterNp); err != nil { - logger.Error(err, "failed to create ClusterNimbusPolicy", "ClusterNimbusPolicy.Name", clusterNp.Name) - return ctrl.Result{}, err - } - return ctrl.Result{}, nil + if err := r.Create(ctx, clusterNp); err != nil { + logger.Error(err, "failed to create ClusterNimbusPolicy", "ClusterNimbusPolicy.Name", clusterNp.Name) + return err + } + logger.Info("ClusterNimbusPolicy created", "ClusterNimbusPolicy.Name", clusterNp.Name) + + return nil +} + +func (r *ClusterSecurityIntentBindingReconciler) updateCwnp(ctx context.Context, logger logr.Logger, csib v1.ClusterSecurityIntentBinding) error { + var existingCwnp v1.ClusterNimbusPolicy + if err := r.Get(ctx, types.NamespacedName{Name: csib.Name}, &existingCwnp); err != nil { + logger.Error(err, "failed to fetch ClusterNimbusPolicy", "ClusterNimbusPolicy.Name", csib.Name) + return err + } + + clusterNp, err := policybuilder.BuildClusterNimbusPolicy(ctx, logger, r.Client, r.Scheme, csib) + if err != nil { + logger.Error(err, "failed to build ClusterNimbusPolicy") + return err } - clusterNp.ObjectMeta = existingClusterNp.ObjectMeta + + clusterNp.ObjectMeta.ResourceVersion = existingCwnp.ObjectMeta.ResourceVersion if err := r.Update(ctx, clusterNp); err != nil { - logger.Error(err, "failed to update ClusterNimbusPolicy") - return ctrl.Result{}, err + logger.Error(err, "failed to configure ClusterNimbusPolicy", "ClusterNimbusPolicy.Name", clusterNp.Name) + return err } + logger.Info("ClusterNimbusPolicy configured", "ClusterNimbusPolicy.Name", clusterNp.Name) - if clusterNp.Status.Status == "" || clusterNp.Status.Status == StatusPending { - clusterNp.Status.Status = StatusCreated - if err := r.Status().Update(ctx, clusterNp); err != nil { - logger.Error(err, "failed to update ClusterNimbusPolicy status", "ClusterNimbusPolicy.Name", clusterNp.Name) - return ctrl.Result{}, err + return nil +} + +func (r *ClusterSecurityIntentBindingReconciler) updateStatus(ctx context.Context, logger logr.Logger, req ctrl.Request) error { + // To handle potential latency issues with the Kubernetes API server, we + // implement an exponential backoff strategy when fetching the ClusterNimbusPolicy + // custom resource. This enhances resilience by retrying failed requests with + // increasing intervals, preventing excessive retries in case of persistent 'Not + // Found' errors. + if retryErr := retry.OnError(retry.DefaultRetry, apierrors.IsNotFound, func() error { + np := &v1.ClusterNimbusPolicy{} + if err := r.Get(ctx, req.NamespacedName, np); err != nil { + return err } - logger.Info("ClusterNimbusPolicy created", "ClusterNimbusPolicy.Name", clusterNp.Name) + return nil + }); retryErr != nil { + logger.Error(retryErr, "failed to fetch ClusterNimbusPolicy", "ClusterNimbusPolicy.Name", req.Name) + return retryErr } - return ctrl.Result{}, nil -} + // Since multiple adapters may update the ClusterNimbusPolicy status concurrently, + // there's a risk of conflict during updates of ClusterNimbusPolicy status. To ensure + // data consistency, retry on write failures. On conflict, the status update is + // retried with an exponential backoff strategy. This provides resilience against + // potential issues while preventing indefinite retries in case of persistent + // conflicts. + if retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + latestCwnp := &v1.ClusterNimbusPolicy{} + if err := r.Get(ctx, req.NamespacedName, latestCwnp); err != nil { + return err + } + latestCwnp.Status = v1.ClusterNimbusPolicyStatus{ + Status: StatusCreated, + LastUpdated: metav1.Now(), + } + if err := r.Status().Update(ctx, latestCwnp); err != nil { + return err + } + return nil + }); retryErr != nil { + logger.Error(retryErr, "failed to update ClusterNimbusPolicy status", "ClusterNimbusPolicy.Name", req.Name) + return retryErr + } -// SetupWithManager sets up the controller with the Manager. -func (r *ClusterSecurityIntentBindingReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&v1.ClusterSecurityIntentBinding{}). - Owns(&v1.ClusterNimbusPolicy{}). - Complete(r) + // Fetch the latest SecurityIntentBinding so that we have the latest state + // on the cluster. + latestCsib := &v1.ClusterSecurityIntentBinding{} + if err := r.Get(ctx, req.NamespacedName, latestCsib); err != nil { + logger.Error(err, "failed to fetch ClusterSecurityIntentBinding", "ClusterSecurityIntentBinding.Name", req.Name) + return err + } + count, boundIntents := extractBoundIntentsInfo(latestCsib.Spec.Intents) + latestCsib.Status = v1.ClusterSecurityIntentBindingStatus{ + Status: StatusCreated, + LastUpdated: metav1.Now(), + NumberOfBoundIntents: count, + BoundIntents: boundIntents, + ClusterNimbusPolicy: req.Name, + } + if err := r.Status().Update(ctx, latestCsib); err != nil { + logger.Error(err, "failed to update ClusterSecurityIntentBinding status", "ClusterSecurityIntentBinding.Name", latestCsib.Name) + return err + } + + return nil } diff --git a/internal/controller/securityintent_controller.go b/internal/controller/securityintent_controller.go index a00b09e3..4976c72a 100644 --- a/internal/controller/securityintent_controller.go +++ b/internal/controller/securityintent_controller.go @@ -6,13 +6,13 @@ package controller import ( "context" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" v1 "github.com/5GSEC/nimbus/api/v1" ) @@ -29,101 +29,46 @@ type SecurityIntentReconciler struct { // move the current state of the cluster closer to the desired state. func (r *SecurityIntentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) - si := &v1.SecurityIntent{} + si := &v1.SecurityIntent{} err := r.Get(ctx, types.NamespacedName{Name: req.Name}, si) if err != nil { - if errors.IsNotFound(err) { + if apierrors.IsNotFound(err) { logger.Info("SecurityIntent not found. Ignoring since object must be deleted") - // When SI is deleted, we should trigger update for related SIBs - if err := r.updateRelatedSIBs(ctx, req); err != nil { - logger.Error(err, "failed to update related SecurityIntentBindings after SI deletion", "SecurityIntent.Name", req.Name) - return ctrl.Result{}, err - } - return ctrl.Result{}, nil + return doNotRequeue() } - logger.Error(err, "failed to get SecurityIntent", "SecurityIntent.Name", req.Name) - return ctrl.Result{}, err + logger.Error(err, "failed to fetch SecurityIntent", "SecurityIntent.Name", req.Name) + return requeueWithError(err) } - var sibList v1.SecurityIntentBindingList - if err := r.List(ctx, &sibList, client.InNamespace(req.Namespace)); err != nil { - logger.Error(err, "unable to list SecurityIntentBindings for update") - return ctrl.Result{}, err + if err = r.updateStatus(ctx, req.Name); err != nil { + logger.Error(err, "failed to update SecurityIntent status", "SecurityIntent.Name", req.Name) + return requeueWithError(err) } + logger.Info("SecurityIntent found", "SecurityIntent.Name", si.Name) - for i := range sibList.Items { - sib := &sibList.Items[i] - for _, intentRef := range sib.Spec.Intents { - if intentRef.Name == req.Name { - sib.Status.LastUpdated = metav1.Now() - if err := r.Status().Update(ctx, sib); err != nil { - logger.Error(err, "failed to update SecurityIntentBinding status for SI update", "SecurityIntentBinding.Name", sib.Name) - return ctrl.Result{}, err - } - logger.Info("Updated SecurityIntentBinding due to SecurityIntent change", "SecurityIntentBinding", sib.Name, "SecurityIntent", req.Name) - break - } - } - } - - if si.Status.Status == "" || si.Status.Status == StatusPending { - si.Status.Status = StatusCreated - if err = r.Status().Update(ctx, si); err != nil { - logger.Error(err, "failed to update SecurityIntent status", "SecurityIntent.Name", si.Name) - return ctrl.Result{}, err - } - - // Let's re-fetch the SecurityIntent Custom Resource after updating the status so - // that we have the latest state of the resource on the cluster. - if err = r.Get(ctx, types.NamespacedName{Name: si.Name, Namespace: si.Namespace}, si); err != nil { - logger.Error(err, "failed to re-fetch SecurityIntent", "SecurityIntent.Name", si.Name) - return ctrl.Result{}, err - } - - logger.Info("SecurityIntent found", "SecurityIntent.Name", si.Name) - } - - return ctrl.Result{}, nil -} - -// Update related SecurityIntentBindings after SecurityIntent deletion -func (r *SecurityIntentReconciler) updateRelatedSIBs(ctx context.Context, req ctrl.Request) error { - var sibList v1.SecurityIntentBindingList - if err := r.List(ctx, &sibList, client.InNamespace(req.Namespace)); err != nil { - return err - } - - logger := log.FromContext(ctx) - - for _, sib := range sibList.Items { - sibCopy := sib - updated := false - for idx, intentRef := range sibCopy.Spec.Intents { - if intentRef.Name == req.Name { - // Remove the reference to the deleted or updated SecurityIntent - sibCopy.Spec.Intents = append(sibCopy.Spec.Intents[:idx], sibCopy.Spec.Intents[idx+1:]...) - updated = true - break - } - } - if updated { - // Mark SIB as needing an update - if err := r.Update(ctx, &sibCopy); err != nil { // 수정된 복사본 사용 - logger.Error(err, "Failed to update SecurityIntentBinding after SI deletion/update", "SecurityIntentBinding.Name", sibCopy.Name) - return err - } - logger.Info("Updated SecurityIntentBinding due to SecurityIntent deletion/update", "SecurityIntentBinding", sibCopy.Name, "SecurityIntent", req.Name) - } - } - - return nil + return doNotRequeue() } // SetupWithManager sets up the reconciler with the provided manager. func (r *SecurityIntentReconciler) SetupWithManager(mgr ctrl.Manager) error { - // Set up the controller to manage SecurityIntent resources. return ctrl.NewControllerManagedBy(mgr). For(&v1.SecurityIntent{}). + WithEventFilter( + predicate.GenerationChangedPredicate{}, + ). Complete(r) } + +func (r *SecurityIntentReconciler) updateStatus(ctx context.Context, name string) error { + latestSi := &v1.SecurityIntent{} + if getErr := r.Get(ctx, types.NamespacedName{Name: name}, latestSi); getErr != nil { + return getErr + } + latestSi.Status = v1.SecurityIntentStatus{ + ID: latestSi.Spec.Intent.ID, + Action: latestSi.Spec.Intent.Action, + Status: StatusCreated, + } + return r.Status().Update(ctx, latestSi) +} diff --git a/internal/controller/securityintentbinding_controller.go b/internal/controller/securityintentbinding_controller.go index 2828161b..6b781363 100644 --- a/internal/controller/securityintentbinding_controller.go +++ b/internal/controller/securityintentbinding_controller.go @@ -5,16 +5,21 @@ package controller import ( "context" + "strings" - "k8s.io/apimachinery/pkg/api/errors" + "github.com/go-logr/logr" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" v1 "github.com/5GSEC/nimbus/api/v1" - "github.com/5GSEC/nimbus/pkg/processor/intentbinder" "github.com/5GSEC/nimbus/pkg/processor/policybuilder" ) @@ -37,91 +42,199 @@ func (r *SecurityIntentBindingReconciler) Reconcile(ctx context.Context, req ctr sib := &v1.SecurityIntentBinding{} err := r.Get(ctx, req.NamespacedName, sib) if err != nil { - if errors.IsNotFound(err) { + if apierrors.IsNotFound(err) { logger.Info("SecurityIntentBinding not found. Ignoring since object must be deleted") - return ctrl.Result{}, nil + logger.Info("NimbusPolicy deleted due to SecurityIntentBinding deletion", + "NimbusPolicy.Name", req.Name, "NimbusPolicy.Namespace", req.Namespace, + "SecurityIntentBinding.Name", req.Name, "SecurityIntentBinding.Namespace", req.Namespace) + return doNotRequeue() } - logger.Error(err, "failed to get SecurityIntentBinding", "SecurityIntentBinding.Name", req.Name, "SecurityIntentBinding.Namespace", req.Namespace) - return ctrl.Result{}, err + logger.Error(err, "failed to fetch SecurityIntentBinding", "SecurityIntentBinding.Name", req.Name, "SecurityIntentBinding.Namespace", req.Namespace) + return requeueWithError(err) } + logger.Info("SecurityIntentBinding found", "SecurityIntentBinding.Name", req.Name, "SecurityIntentBinding.Namespace", req.Namespace) - if sib.Status.Status == "" { - sib.Status.Status = StatusCreated - sib.Status.LastUpdated = metav1.Now() - if err := r.Status().Update(ctx, sib); err != nil { - logger.Error(err, "failed to update SecurityIntentBinding status", "SecurityIntentBinding.Name", sib.Name, "SecurityIntentBinding.Namespace", sib.Namespace) - return ctrl.Result{}, err - } - // Let's re-fetch the SecurityIntentBinding CR after updating the status so that - // we have the latest state of the resource on the cluster. - if err := r.Get(ctx, req.NamespacedName, sib); err != nil { - logger.Error(err, "failed to re-fetch SecurityIntentBinding", "SecurityIntentBinding.Name", sib.Name, "SecurityIntentBinding.Namespace", sib.Namespace) - return ctrl.Result{}, err + if err = r.createOrUpdateNp(ctx, logger, req); err != nil { + return requeueWithError(err) + } + + if err = r.updateStatus(ctx, logger, req); err != nil { + return requeueWithError(err) + } + + return doNotRequeue() +} + +// SetupWithManager sets up the controller with the Manager. +func (r *SecurityIntentBindingReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1.SecurityIntentBinding{}). + Owns(&v1.NimbusPolicy{}). + WithEventFilter( + predicate.Funcs{ + CreateFunc: r.createFn, + UpdateFunc: r.updateFn, + DeleteFunc: r.deleteFn, + }, + ). + Complete(r) +} + +func (r *SecurityIntentBindingReconciler) createFn(createEvent event.CreateEvent) bool { + if _, ok := createEvent.Object.(*v1.NimbusPolicy); ok { + return false + } + return true +} + +func (r *SecurityIntentBindingReconciler) updateFn(updateEvent event.UpdateEvent) bool { + // TODO: Handle update event for NimbusPolicy update so that reconciler don't process it + // twice. + return updateEvent.ObjectOld.GetGeneration() != updateEvent.ObjectNew.GetGeneration() +} + +func (r *SecurityIntentBindingReconciler) deleteFn(deleteEvent event.DeleteEvent) bool { + obj := deleteEvent.Object + if _, ok := obj.(*v1.SecurityIntentBinding); ok { + return true + } + return ownerExists(r.Client, obj) +} + +func (r *SecurityIntentBindingReconciler) createOrUpdateNp(ctx context.Context, logger logr.Logger, req ctrl.Request) error { + // Always fetch the latest CRs so that we have the latest state of the CRs on the + // cluster. + + var sib v1.SecurityIntentBinding + if err := r.Get(ctx, req.NamespacedName, &sib); err != nil { + logger.Error(err, "failed to fetch SecurityIntentBinding", "SecurityIntentBinding.Name", req.Name, "SecurityIntentBinding.Namespace", req.Namespace) + return err + } + + var np v1.NimbusPolicy + err := r.Get(ctx, req.NamespacedName, &np) + if err != nil { + if apierrors.IsNotFound(err) { + return r.createNp(ctx, logger, sib) } - logger.Info("SecurityIntentBinding found", "SecurityIntentBinding.Name", sib.Name, "SecurityIntentBinding.Namespace", sib.Namespace) - return ctrl.Result{}, nil + logger.Error(err, "failed to fetch NimbusPolicy", "NimbusPolicy.Name", req.Name, "NimbusPolicy.Namespace", req.Namespace) + return err } + return r.updateNp(ctx, logger, sib) +} - bindingInfo := intentbinder.MatchAndBindIntents(ctx, r.Client, sib) - nimbusPolicy, err := policybuilder.BuildNimbusPolicy(ctx, r.Client, r.Scheme, bindingInfo) +func (r *SecurityIntentBindingReconciler) createNp(ctx context.Context, logger logr.Logger, sib v1.SecurityIntentBinding) error { + nimbusPolicy, err := policybuilder.BuildNimbusPolicy(ctx, logger, r.Client, r.Scheme, sib) + // TODO: Improve error handling for CEL if err != nil { + // If error is caused due to CEL then we don't retry to build NimbusPolicy. + if strings.Contains(err.Error(), "error processing CEL") { + logger.Error(err, "failed to build NimbusPolicy") + return nil + } logger.Error(err, "failed to build NimbusPolicy") - return ctrl.Result{}, err + return err } - existingNp := &v1.NimbusPolicy{} - err = r.Get(ctx, req.NamespacedName, existingNp) - if err != nil && errors.IsNotFound(err) { - if err := r.Create(ctx, nimbusPolicy); err != nil { - logger.Error(err, "failed to create NimbusPolicy", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) - return ctrl.Result{}, err - } - logger.Info("NimbusPolicy created", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) - } else if err == nil { - nimbusPolicy.ObjectMeta.ResourceVersion = existingNp.ObjectMeta.ResourceVersion - - // Check if np needs to be updated - if sibChanged(sib, existingNp) { - nimbusPolicy.Status.LastUpdated = metav1.Now() - if err := r.Update(ctx, nimbusPolicy); err != nil { - logger.Error(err, "failed to update NimbusPolicy") - return ctrl.Result{}, err - } - logger.Info("NimbusPolicy updated", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) - - if err := r.updateSIBStatus(ctx, sib); err != nil { - logger.Error(err, "failed to update SecurityIntentBinding status after NimbusPolicy operation", "SecurityIntentBinding.Name", sib.Name, "SecurityIntentBinding.Namespace", sib.Namespace) - return ctrl.Result{}, err - } - return ctrl.Result{}, err - } + if err := r.Create(ctx, nimbusPolicy); err != nil { + logger.Error(err, "failed to create NimbusPolicy", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) + return err + } + logger.Info("NimbusPolicy created", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) + + return nil +} + +func (r *SecurityIntentBindingReconciler) updateNp(ctx context.Context, logger logr.Logger, sib v1.SecurityIntentBinding) error { + var existingNp v1.NimbusPolicy + if err := r.Get(ctx, types.NamespacedName{Name: sib.Name, Namespace: sib.Namespace}, &existingNp); err != nil { + logger.Error(err, "failed to fetch NimbusPolicy", "NimbusPolicy.Name", sib.Name, "NimbusPolicy.Namespace", sib.Namespace) + return err } - if nimbusPolicy.Status.Status == "" || nimbusPolicy.Status.Status == StatusPending { - nimbusPolicy.Status.Status = StatusCreated - nimbusPolicy.Status.LastUpdated = metav1.Now() - if err := r.Status().Update(ctx, nimbusPolicy); err != nil { - logger.Error(err, "failed to update NimbusPolicy status", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) - return ctrl.Result{}, err + nimbusPolicy, err := policybuilder.BuildNimbusPolicy(ctx, logger, r.Client, r.Scheme, sib) + // TODO: Improve error handling for CEL + if err != nil { + // If error is caused due to CEL then we don't retry to build NimbusPolicy. + if strings.Contains(err.Error(), "error processing CEL") { + logger.Error(err, "failed to build NimbusPolicy") + return nil } - logger.Info("NimbusPolicy created", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) + logger.Error(err, "failed to build NimbusPolicy") + return err } - return ctrl.Result{}, nil -} -func sibChanged(sib *v1.SecurityIntentBinding, np *v1.NimbusPolicy) bool { - return sib.Status.LastUpdated.Time.Before(np.Status.LastUpdated.Time) -} + nimbusPolicy.ObjectMeta.ResourceVersion = existingNp.ObjectMeta.ResourceVersion + if err := r.Update(ctx, nimbusPolicy); err != nil { + logger.Error(err, "failed to configure NimbusPolicy", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) + return err + } + logger.Info("NimbusPolicy configured", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) -func (r *SecurityIntentBindingReconciler) updateSIBStatus(ctx context.Context, sib *v1.SecurityIntentBinding) error { - sib.Status.LastUpdated = metav1.Now() - return r.Status().Update(ctx, sib) + return nil } -// SetupWithManager sets up the controller with the Manager. -func (r *SecurityIntentBindingReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&v1.SecurityIntentBinding{}). - Owns(&v1.NimbusPolicy{}). - Complete(r) +func (r *SecurityIntentBindingReconciler) updateStatus(ctx context.Context, logger logr.Logger, req ctrl.Request) error { + // To handle potential latency issues with the Kubernetes API server, we + // implement an exponential backoff strategy when fetching the NimbusPolicy + // custom resource. This enhances resilience by retrying failed requests with + // increasing intervals, preventing excessive retries in case of persistent 'Not + // Found' errors. + if retryErr := retry.OnError(retry.DefaultRetry, apierrors.IsNotFound, func() error { + np := &v1.NimbusPolicy{} + if err := r.Get(ctx, req.NamespacedName, np); err != nil { + return err + } + return nil + }); retryErr != nil { + logger.Error(retryErr, "failed to fetch NimbusPolicy", "NimbusPolicy.Name", req.Name, "NimbusPolicy.Namespace", req.Namespace) + return retryErr + } + + // Since multiple adapters may update the NimbusPolicy status concurrently, + // there's a risk of conflict during updates of NimbusPolicy status. To ensure + // data consistency, retry on write failures. On conflict, the status update is + // retried with an exponential backoff strategy. This provides resilience against + // potential issues while preventing indefinite retries in case of persistent + // conflicts. + if retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + latestNp := &v1.NimbusPolicy{} + if err := r.Get(ctx, req.NamespacedName, latestNp); err != nil { + return err + } + + latestNp.Status = v1.NimbusPolicyStatus{ + Status: StatusCreated, + LastUpdated: metav1.Now(), + } + if err := r.Status().Update(ctx, latestNp); err != nil { + return err + } + return nil + }); retryErr != nil { + logger.Error(retryErr, "failed to update NimbusPolicy status", "NimbusPolicy.Name", req.Name, "NimbusPolicy.Namespace", req.Namespace) + return retryErr + } + + // Fetch the latest SecurityIntentBinding so that we have the latest state + // on the cluster. + latestSib := &v1.SecurityIntentBinding{} + if err := r.Get(ctx, req.NamespacedName, latestSib); err != nil { + logger.Error(err, "failed to fetch SecurityIntentBinding", "SecurityIntentBinding.Name", req.Name, "SecurityIntentBinding.Namespace", req.Namespace) + return err + } + count, boundIntents := extractBoundIntentsInfo(latestSib.Spec.Intents) + latestSib.Status = v1.SecurityIntentBindingStatus{ + Status: StatusCreated, + LastUpdated: metav1.Now(), + NumberOfBoundIntents: count, + BoundIntents: boundIntents, + NimbusPolicy: req.Name, + } + if err := r.Status().Update(ctx, latestSib); err != nil { + logger.Error(err, "failed to update SecurityIntentBinding status", "SecurityIntentBinding.Name", req.Name, "SecurityIntentBinding.Namespace", req.Namespace) + return err + } + + return nil } diff --git a/internal/controller/status.go b/internal/controller/status.go deleted file mode 100644 index 1b840e87..00000000 --- a/internal/controller/status.go +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2023 Authors of Nimbus - -package controller - -const ( - StatusPending = "Pending" - StatusCreated = "Created" -) diff --git a/internal/controller/util.go b/internal/controller/util.go new file mode 100644 index 00000000..43118b30 --- /dev/null +++ b/internal/controller/util.go @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Authors of Nimbus + +package controller + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + v1 "github.com/5GSEC/nimbus/api/v1" +) + +// TODO: Add constants for recommend labels and update objects accordingly. +// https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ + +const ( + StatusCreated = "Created" +) + +func doNotRequeue() (ctrl.Result, error) { + return ctrl.Result{}, nil +} + +func requeueWithError(err error) (ctrl.Result, error) { + return ctrl.Result{}, err +} + +func extractBoundIntentsInfo(intents []v1.MatchIntent) (int32, []string) { + var count int32 + var names []string + for _, intent := range intents { + count++ + names = append(names, intent.Name) + } + return count, names +} + +func ownerExists(c client.Client, controllee client.Object) bool { + // Don't even try to look if it has no ControllerRef. + controller := metav1.GetControllerOf(controllee) + if controller == nil { + return false + } + + ownerName := controller.Name + ownerUid := controller.UID + var objToGet client.Object + + switch controllee.(type) { + case *v1.SecurityIntentBinding: + objToGet = &v1.SecurityIntentBinding{} + case *v1.ClusterSecurityIntentBinding: + objToGet = &v1.ClusterSecurityIntentBinding{} + } + + if err := c.Get(context.Background(), types.NamespacedName{Name: ownerName, Namespace: controllee.GetNamespace()}, objToGet); err != nil { + return false + } + + // Verify whether the controller we found is same that the ControllerRef points + // to. + return objToGet.GetUID() == ownerUid +} diff --git a/pkg/adapter/nimbus-kubearmor/go.sum b/pkg/adapter/nimbus-kubearmor/go.sum index 8e5bfd38..27d5382d 100644 --- a/pkg/adapter/nimbus-kubearmor/go.sum +++ b/pkg/adapter/nimbus-kubearmor/go.sum @@ -1,5 +1,3 @@ -github.com/5GSEC/nimbus v0.0.0-20240129090659-01178b5c28c7 h1:adBGcrCAKeU7PLiz6m2c+3c8uuL5UPkHN5O6FHJQm7I= -github.com/5GSEC/nimbus v0.0.0-20240129090659-01178b5c28c7/go.mod h1:VXo/w78XDmQEunuZYIsDyGDthCKealQR13X9OkY25D0= github.com/5GSEC/nimbus v0.0.0-20240208070656-624660f34768 h1:v2fY3lWXydstfekQSHs9n0TpNnTteC7Iws3ojwGtFJk= github.com/5GSEC/nimbus v0.0.0-20240208070656-624660f34768/go.mod h1:yw79m9f1+f3tBSZCMQKbNVKL39Q71FyGyoa8nClo1Hs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/pkg/adapter/nimbus-kubearmor/manager/manager.go b/pkg/adapter/nimbus-kubearmor/manager/manager.go index d6ce175a..f360ee48 100644 --- a/pkg/adapter/nimbus-kubearmor/manager/manager.go +++ b/pkg/adapter/nimbus-kubearmor/manager/manager.go @@ -29,7 +29,6 @@ import ( var ( scheme = runtime.NewScheme() - np intentv1.NimbusPolicy k8sClient client.Client ) @@ -81,6 +80,7 @@ func Run(ctx context.Context) { func reconcileKsp(ctx context.Context, kspName, namespace string, deleted bool) { logger := log.FromContext(ctx) npName := adapterutil.ExtractNpName(kspName) + var np intentv1.NimbusPolicy err := k8sClient.Get(ctx, types.NamespacedName{Name: npName, Namespace: namespace}, &np) if err != nil { if !errors.IsNotFound(err) { @@ -98,18 +98,22 @@ func reconcileKsp(ctx context.Context, kspName, namespace string, deleted bool) func createOrUpdateKsp(ctx context.Context, npName, npNamespace string) { logger := log.FromContext(ctx) + var np intentv1.NimbusPolicy if err := k8sClient.Get(ctx, types.NamespacedName{Name: npName, Namespace: npNamespace}, &np); err != nil { logger.Error(err, "failed to get NimbusPolicy", "NimbusPolicy.Name", npName, "NimbusPolicy.Namespace", npNamespace) return } if adapterutil.IsOrphan(np.GetOwnerReferences(), "SecurityIntentBinding") { - logger.V(4).Info("Ignoring orphan NimbusPolicy", "NimbusPolicy.Name", np.GetName(), "NimbusPolicy.Namespace", np.GetNamespace()) + logger.V(4).Info("Ignoring orphan NimbusPolicy", "NimbusPolicy.Name", npName, "NimbusPolicy.Namespace", npNamespace) return } ksps := processor.BuildKspsFrom(logger, &np) - deleteUnnecessaryKsps(ctx, ksps, npNamespace, logger) + + // TODO: Fix loop due to unnecessary KSPs deletion + //deleteUnnecessaryKsps(ctx, ksps, npNamespace, logger) + // Iterate using a separate index variable to avoid aliasing for idx := range ksps { ksp := ksps[idx] @@ -140,6 +144,18 @@ func createOrUpdateKsp(ctx context.Context, npName, npNamespace string) { } logger.Info("KubeArmorPolicy configured", "KubeArmorPolicy.Name", existingKsp.Name, "KubeArmorPolicy.Namespace", existingKsp.Namespace) } + + // Due to adapters' dependency on nimbus module, the docker image build is + // failing. The relevant code is commented out below (lines 153-155). We shall + // uncomment this code in a subsequent PR. + + // Every adapter is responsible for updating the status field of the + // corresponding NimbusPolicy with the number and names of successfully created + // policies. This provides feedback to users about the translation and deployment + // of their security intent. + //if err = adapterutil.UpdateNpStatus(ctx, k8sClient, "KubeArmorPolicy/"+ksp.Name, np.Name, np.Namespace); err != nil { + // logger.Error(err, "failed to update KubeArmorPolicies status in NimbusPolicy") + //} } } diff --git a/pkg/adapter/nimbus-kubearmor/processor/kspbuilder.go b/pkg/adapter/nimbus-kubearmor/processor/kspbuilder.go index 40338409..0b5c3316 100644 --- a/pkg/adapter/nimbus-kubearmor/processor/kspbuilder.go +++ b/pkg/adapter/nimbus-kubearmor/processor/kspbuilder.go @@ -83,6 +83,7 @@ func unAuthorizedSaTokenAccessKsp() kubearmorv1.KubeArmorPolicy { } } +// TODO: Instead of downloading the KSP build it locally func swDeploymentToolsKsp() kubearmorv1.KubeArmorPolicy { var ksp kubearmorv1.KubeArmorPolicy fileUrl := "https://raw.githubusercontent.com/kubearmor/policy-templates/main/nist/system/ksp-nist-si-4-execute-package-management-process-in-container.yaml" diff --git a/pkg/adapter/nimbus-netpol/go.mod b/pkg/adapter/nimbus-netpol/go.mod index 1b82b8ba..59f5c915 100644 --- a/pkg/adapter/nimbus-netpol/go.mod +++ b/pkg/adapter/nimbus-netpol/go.mod @@ -16,7 +16,6 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.2 // indirect - github.com/evanphx/json-patch v5.7.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/zapr v1.3.0 // indirect diff --git a/pkg/adapter/nimbus-netpol/go.sum b/pkg/adapter/nimbus-netpol/go.sum index 40de45ce..72c856d2 100644 --- a/pkg/adapter/nimbus-netpol/go.sum +++ b/pkg/adapter/nimbus-netpol/go.sum @@ -1,5 +1,3 @@ -github.com/5GSEC/nimbus v0.0.0-20240129090659-01178b5c28c7 h1:adBGcrCAKeU7PLiz6m2c+3c8uuL5UPkHN5O6FHJQm7I= -github.com/5GSEC/nimbus v0.0.0-20240129090659-01178b5c28c7/go.mod h1:VXo/w78XDmQEunuZYIsDyGDthCKealQR13X9OkY25D0= github.com/5GSEC/nimbus v0.0.0-20240208070656-624660f34768 h1:v2fY3lWXydstfekQSHs9n0TpNnTteC7Iws3ojwGtFJk= github.com/5GSEC/nimbus v0.0.0-20240208070656-624660f34768/go.mod h1:yw79m9f1+f3tBSZCMQKbNVKL39Q71FyGyoa8nClo1Hs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/pkg/adapter/nimbus-netpol/manager/netpols_manager.go b/pkg/adapter/nimbus-netpol/manager/netpols_manager.go index 86a75a35..c66c32c8 100644 --- a/pkg/adapter/nimbus-netpol/manager/netpols_manager.go +++ b/pkg/adapter/nimbus-netpol/manager/netpols_manager.go @@ -28,7 +28,6 @@ import ( var ( scheme = runtime.NewScheme() - np intentv1.NimbusPolicy k8sClient client.Client ) @@ -80,6 +79,7 @@ func Run(ctx context.Context) { func reconcileNetPol(ctx context.Context, netpolName, namespace string, deleted bool) { logger := log.FromContext(ctx) npName := adapterutil.ExtractNpName(netpolName) + var np intentv1.NimbusPolicy err := k8sClient.Get(ctx, types.NamespacedName{Name: npName, Namespace: namespace}, &np) if err != nil { if !errors.IsNotFound(err) { @@ -97,13 +97,14 @@ func reconcileNetPol(ctx context.Context, netpolName, namespace string, deleted func createOrUpdateNetworkPolicy(ctx context.Context, npName, npNamespace string) { logger := log.FromContext(ctx) + var np intentv1.NimbusPolicy if err := k8sClient.Get(ctx, types.NamespacedName{Name: npName, Namespace: npNamespace}, &np); err != nil { logger.Error(err, "failed to get NimbusPolicy", "NimbusPolicy.Name", npName[0], "NimbusPolicy.Namespace", npName[1]) return } if adapterutil.IsOrphan(np.GetOwnerReferences(), "SecurityIntentBinding") { - logger.V(4).Info("Ignoring orphan NimbusPolicy", "NimbusPolicy.Name", np.GetName(), "NimbusPolicy.Namespace", np.GetNamespace()) + logger.V(4).Info("Ignoring orphan NimbusPolicy", "NimbusPolicy.Name", npName, "NimbusPolicy.Namespace", npNamespace) return } @@ -138,6 +139,18 @@ func createOrUpdateNetworkPolicy(ctx context.Context, npName, npNamespace string } logger.Info("NetworkPolicy configured", "NetworkPolicy.Name", netpol.Name, "NetworkPolicy.Namespace", netpol.Namespace) } + + // Due to adapters' dependency on nimbus module, the docker image build is + // failing. The relevant code is commented out below (lines 153-155). We shall + // uncomment this code in a subsequent PR. + + // Every adapter is responsible for updating the status field of the + // corresponding NimbusPolicy with the number and names of successfully created + // policies. This provides feedback to users about the translation and deployment + // of their security intent. + //if err = adapterutil.UpdateNpStatus(ctx, k8sClient, "NetworkPolicy/"+netpol.Name, np.Name, np.Namespace); err != nil { + // logger.Error(err, "failed to update NetworkPolicies status in NimbusPolicy") + //} } } diff --git a/pkg/adapter/util/nimbuspolicy_util.go b/pkg/adapter/util/nimbuspolicy_util.go index 3889c0b1..2579971a 100644 --- a/pkg/adapter/util/nimbuspolicy_util.go +++ b/pkg/adapter/util/nimbuspolicy_util.go @@ -4,10 +4,61 @@ package util import ( + "context" "strings" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + + intentv1 "github.com/5GSEC/nimbus/api/v1" ) -func ExtractNpName(kspName string) string { - words := strings.Split(kspName, "-") +// ExtractNpName extracts the actual NimbusPolicy name from a formatted policy +// name. +func ExtractNpName(policyName string) string { + words := strings.Split(policyName, "-") return strings.Join(words[:len(words)-1], "-") } + +// UpdateNpStatus updates the provided NimbusPolicy status with the number and +// names of its descendant policies that were created. +func UpdateNpStatus(ctx context.Context, k8sClient client.Client, currPolicyFullName, npName, namespace string) error { + // Since multiple adapters may attempt to update the NimbusPolicy status + // concurrently, potentially leading to conflicts. To ensure data consistency, + // retry on write failures. On conflict, the update is retried with an + // exponential backoff strategy. This provides resilience against potential + // issues while preventing indefinite retries in case of persistent conflicts. + if retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + latestNp := &intentv1.NimbusPolicy{} + if err := k8sClient.Get(ctx, types.NamespacedName{Name: npName, Namespace: namespace}, latestNp); err != nil { + return nil + } + + updateCountAndPoliciesName(latestNp, currPolicyFullName) + if err := k8sClient.Status().Update(ctx, latestNp); err != nil { + return err + } + + return nil + }); retryErr != nil { + return retryErr + } + return nil +} + +func updateCountAndPoliciesName(latestNp *intentv1.NimbusPolicy, currPolicyFullName string) { + if !contains(latestNp.Status.Policies, currPolicyFullName) { + latestNp.Status.NumberOfAdapterPolicies++ + latestNp.Status.Policies = append(latestNp.Status.Policies, currPolicyFullName) + } +} + +func contains(existingPolicies []string, policy string) bool { + for _, existingPolicy := range existingPolicies { + if existingPolicy == policy { + return true + } + } + return false +} diff --git a/pkg/processor/intentbinder/intent_binder.go b/pkg/processor/intentbinder/intent_binder.go index d09783b7..ed55feaf 100644 --- a/pkg/processor/intentbinder/intent_binder.go +++ b/pkg/processor/intentbinder/intent_binder.go @@ -6,6 +6,7 @@ package intentbinder import ( "context" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -13,64 +14,28 @@ import ( v1 "github.com/5GSEC/nimbus/api/v1" ) -// BindingInfo holds the names of matched SecurityIntent and SecurityIntentBinding. -type BindingInfo struct { - IntentNames []string - BindingNames []string - BindingNamespaces []string -} - -// NewBindingInfo creates a new instance of BindingInfo. -func NewBindingInfo(intentNames []string, bindingNames []string, bindingNamespaces []string) *BindingInfo { - return &BindingInfo{ - IntentNames: intentNames, - BindingNames: bindingNames, - BindingNamespaces: bindingNamespaces, - } -} - -func MatchAndBindIntents(ctx context.Context, client client.Client, bindings *v1.SecurityIntentBinding) *BindingInfo { +// ExtractIntents extract the SecurityIntent from the given SecurityIntentBinding +// or ClusterSecurityIntentBinding objects. +func ExtractIntents(ctx context.Context, c client.Client, object client.Object) []v1.SecurityIntent { logger := log.FromContext(ctx) - logger.Info("SecurityIntent and SecurityIntentBinding matching started") - - var matchedIntents []string - var matchedBindings []string - var matchedBindingNamespaces []string - - for _, intentRef := range bindings.Spec.Intents { - var intent v1.SecurityIntent - if err := client.Get(ctx, types.NamespacedName{Name: intentRef.Name, Namespace: bindings.Namespace}, &intent); err != nil { - logger.Info("failed to get SecurityIntent", "SecurityIntent.Name", intentRef.Name) - continue - } - matchedIntents = append(matchedIntents, intent.Name) + var intents []v1.SecurityIntent + var givenIntents []v1.MatchIntent + + switch obj := object.(type) { + case *v1.SecurityIntentBinding: + givenIntents = obj.Spec.Intents + case *v1.ClusterSecurityIntentBinding: + givenIntents = obj.Spec.Intents } - // Adding names and namespaces of SecurityIntentBinding. - matchedBindings = append(matchedBindings, bindings.Name) - matchedBindingNamespaces = append(matchedBindingNamespaces, bindings.Namespace) - - logger.Info("Matching completed", "Matched SecurityIntents", matchedIntents, "Matched SecurityIntentsBindings", matchedBindings) - return NewBindingInfo(matchedIntents, matchedBindings, matchedBindingNamespaces) -} - -func MatchAndBindIntentsGlobal(ctx context.Context, client client.Client, clusterBinding *v1.ClusterSecurityIntentBinding) *BindingInfo { - logger := log.FromContext(ctx) - logger.Info("SecurityIntent and ClusterSecurityIntentBinding matching started") - - var matchedIntents []string - for _, intentRef := range clusterBinding.Spec.Intents { - var intent v1.SecurityIntent - if err := client.Get(ctx, types.NamespacedName{Name: intentRef.Name}, &intent); err != nil { - logger.Info("failed to get SecurityIntent", "SecurityIntent.Name", intentRef.Name) + for _, intent := range givenIntents { + var si v1.SecurityIntent + if err := c.Get(ctx, types.NamespacedName{Name: intent.Name}, &si); err != nil && apierrors.IsNotFound(err) { + logger.V(2).Info("failed to fetch SecurityIntent", "SecurityIntent.Name", intent.Name) continue } - matchedIntents = append(matchedIntents, intent.Name) + intents = append(intents, si) } - var matchedClusterBindings []string - matchedClusterBindings = append(matchedClusterBindings, clusterBinding.Name) - - logger.Info("Matching completed", "Matched SecurityIntents", matchedIntents, "Matched ClusterSecurityIntentBindings", matchedClusterBindings) - return NewBindingInfo(matchedIntents, matchedClusterBindings, nil) + return intents } diff --git a/pkg/processor/policybuilder/clusternimbuspolicy_builder.go b/pkg/processor/policybuilder/clusternimbuspolicy_builder.go index 4dab1d6a..bde69035 100644 --- a/pkg/processor/policybuilder/clusternimbuspolicy_builder.go +++ b/pkg/processor/policybuilder/clusternimbuspolicy_builder.go @@ -7,82 +7,60 @@ import ( "context" "fmt" + "github.com/go-logr/logr" + "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" v1 "github.com/5GSEC/nimbus/api/v1" "github.com/5GSEC/nimbus/pkg/processor/intentbinder" ) -func BuildClusterNimbusPolicy(ctx context.Context, client client.Client, scheme *runtime.Scheme, clusterBindingInfo *intentbinder.BindingInfo) (*v1.ClusterNimbusPolicy, error) { - logger := log.FromContext(ctx) +// BuildClusterNimbusPolicy generates a ClusterNimbusPolicy based on given +// SecurityIntents and ClusterSecurityIntentBinding. +func BuildClusterNimbusPolicy(ctx context.Context, logger logr.Logger, k8sClient client.Client, scheme *runtime.Scheme, csib v1.ClusterSecurityIntentBinding) (*v1.ClusterNimbusPolicy, error) { logger.Info("Building ClusterNimbusPolicy") + intents := intentbinder.ExtractIntents(ctx, k8sClient, &csib) + if len(intents) == 0 { + return nil, fmt.Errorf("no SecurityIntents found in the cluster") + } var nimbusRules []v1.NimbusRules - for _, intentName := range clusterBindingInfo.IntentNames { - intent, err := FetchIntentByName(ctx, client, intentName) - if err != nil { - return nil, err - } - - if len(clusterBindingInfo.IntentNames) == 0 || len(clusterBindingInfo.BindingNames) == 0 { - logger.Info("No SecurityIntents or SecurityIntentsBindings to process") - return nil, fmt.Errorf("no SecurityIntents or SecurityIntentsBindings to process") - } - - rule := v1.Rule{ - RuleAction: intent.Spec.Intent.Action, - Mode: intent.Spec.Intent.Mode, - Params: map[string][]string{}, - } - - for key, val := range intent.Spec.Intent.Params { - rule.Params[key] = val - } - - nimbusRule := v1.NimbusRules{ + for _, intent := range intents { + nimbusRules = append(nimbusRules, v1.NimbusRules{ ID: intent.Spec.Intent.ID, - Type: "", // Set Type if necessary Description: intent.Spec.Intent.Description, - Rule: rule, - } - nimbusRules = append(nimbusRules, nimbusRule) - } - - binding, err := fetchClusterBinding(ctx, client, clusterBindingInfo.BindingNames[0]) - if err != nil { - return nil, err + Rule: v1.Rule{ + RuleAction: intent.Spec.Intent.Action, + Params: intent.Spec.Intent.Params, + }, + }) } - clusterBindingSelector := extractClusterBindingSelector(binding.Spec.Selector) - - clusterNimbusPolicy := &v1.ClusterNimbusPolicy{ + clusterBindingSelector := extractClusterBindingSelector(csib.Spec.Selector) + clusterNp := &v1.ClusterNimbusPolicy{ ObjectMeta: metav1.ObjectMeta{ - Name: binding.Name, + Name: csib.Name, + Labels: csib.Labels, }, Spec: v1.ClusterNimbusPolicySpec{ Selector: clusterBindingSelector, NimbusRules: nimbusRules, }, - Status: v1.ClusterNimbusPolicyStatus{ - Status: "Pending", - }, } - if err = ctrl.SetControllerReference(&binding, clusterNimbusPolicy, scheme); err != nil { - logger.Error(err, "failed to set OwnerReference") - return nil, err + if err := ctrl.SetControllerReference(&csib, clusterNp, scheme); err != nil { + return nil, errors.Wrap(err, "failed to set NimbusPolicy OwnerReference") } - logger.Info("ClusterNimbusPolicy built successfully", "ClusterNimbusPolicy", clusterNimbusPolicy) - return clusterNimbusPolicy, nil + logger.Info("ClusterNimbusPolicy built successfully", "ClusterNimbusPolicy.Name", clusterNp.Name) + return clusterNp, nil } func extractClusterBindingSelector(cwSelector v1.CwSelector) v1.CwSelector { + // Todo: Handle CEL expression var clusterBindingSelector v1.CwSelector for _, resource := range cwSelector.Resources { var cwresource v1.CwResource @@ -94,13 +72,3 @@ func extractClusterBindingSelector(cwSelector v1.CwSelector) v1.CwSelector { } return clusterBindingSelector } - -func fetchClusterBinding(ctx context.Context, client client.Client, clusterBindingName string) (v1.ClusterSecurityIntentBinding, error) { - logger := log.FromContext(ctx) - var clusterBinding v1.ClusterSecurityIntentBinding - if err := client.Get(ctx, types.NamespacedName{Name: clusterBindingName}, &clusterBinding); err != nil { - logger.Error(err, "failed to get ClusterSecurityIntentBinding", "ClusterSecurityIntentBinding", clusterBindingName) - return v1.ClusterSecurityIntentBinding{}, err - } - return clusterBinding, nil -} diff --git a/pkg/processor/policybuilder/common.go b/pkg/processor/policybuilder/common.go index 911c57f1..8636e2c5 100644 --- a/pkg/processor/policybuilder/common.go +++ b/pkg/processor/policybuilder/common.go @@ -11,25 +11,12 @@ import ( "github.com/google/cel-go/cel" "github.com/google/cel-go/checker/decls" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" v1 "github.com/5GSEC/nimbus/api/v1" ) -// FetchIntentByName fetches a SecurityIntent by its name. -func FetchIntentByName(ctx context.Context, client client.Client, name string) (*v1.SecurityIntent, error) { - logger := log.FromContext(ctx) - - var intent v1.SecurityIntent - if err := client.Get(ctx, types.NamespacedName{Name: name}, &intent); err != nil { - logger.Error(err, "Failed to get SecurityIntent") - return nil, err - } - return &intent, nil -} - // ProcessCEL processes CEL expressions to generate matchLabels. func ProcessCEL(ctx context.Context, k8sClient client.Client, namespace string, expressions []string) (map[string]string, error) { logger := log.FromContext(ctx) diff --git a/pkg/processor/policybuilder/nimbuspolicy_builder.go b/pkg/processor/policybuilder/nimbuspolicy_builder.go index 31dc79ba..5ad5a131 100644 --- a/pkg/processor/policybuilder/nimbuspolicy_builder.go +++ b/pkg/processor/policybuilder/nimbuspolicy_builder.go @@ -7,113 +7,69 @@ import ( "context" "fmt" + "github.com/go-logr/logr" + "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" v1 "github.com/5GSEC/nimbus/api/v1" "github.com/5GSEC/nimbus/pkg/processor/intentbinder" ) -// BuildNimbusPolicy generates a NimbusPolicy based on SecurityIntent and SecurityIntentBinding. -func BuildNimbusPolicy(ctx context.Context, client client.Client, scheme *runtime.Scheme, bindingInfo *intentbinder.BindingInfo) (*v1.NimbusPolicy, error) { - logger := log.FromContext(ctx) +// BuildNimbusPolicy generates a NimbusPolicy based on given +// SecurityIntentBinding. +func BuildNimbusPolicy(ctx context.Context, logger logr.Logger, k8sClient client.Client, scheme *runtime.Scheme, sib v1.SecurityIntentBinding) (*v1.NimbusPolicy, error) { logger.Info("Building NimbusPolicy") - var nimbusRulesList []v1.NimbusRules - - // Iterate over intent names to build rules. - for _, intentName := range bindingInfo.IntentNames { - intent, err := FetchIntentByName(ctx, client, intentName) - if err != nil { - return nil, err - } - - // Checks if arrays in bindingInfo are empty. - if len(bindingInfo.IntentNames) == 0 || len(bindingInfo.BindingNames) == 0 { - fmt.Println("No intents or bindings to process") - return nil, fmt.Errorf("no intents or bindings to process") - } - - // Constructs a rule from the intent parameters. - rule := v1.Rule{ - RuleAction: intent.Spec.Intent.Action, - Mode: intent.Spec.Intent.Mode, - Params: map[string][]string{}, - } - - for key, val := range intent.Spec.Intent.Params { - rule.Params[key] = val - } + intents := intentbinder.ExtractIntents(ctx, k8sClient, &sib) + if len(intents) == 0 { + return nil, fmt.Errorf("no SecurityIntents found in the cluster") + } - nimbusRule := v1.NimbusRules{ + var nimbusRules []v1.NimbusRules + for _, intent := range intents { + nimbusRules = append(nimbusRules, v1.NimbusRules{ ID: intent.Spec.Intent.ID, - Type: "", // Set Type if necessary Description: intent.Spec.Intent.Description, - Rule: rule, - } - nimbusRulesList = append(nimbusRulesList, nimbusRule) - } - - // Fetches the binding to extract selector. - bindingName := bindingInfo.BindingNames[0] - bindingNamespace := bindingInfo.BindingNamespaces[0] - binding, err := fetchBinding(ctx, client, bindingName, bindingNamespace) - if err != nil { - return nil, err + Rule: v1.Rule{ + RuleAction: intent.Spec.Intent.Action, + Params: intent.Spec.Intent.Params, + }, + }) } - // Extracts match labels from the binding selector. - matchLabels, err := extractSelector(ctx, client, bindingNamespace, binding.Spec.Selector) + matchLabels, err := extractSelector(ctx, k8sClient, sib.Namespace, sib.Spec.Selector) if err != nil { return nil, err } - if len(matchLabels) == 0 { - logger.Error(err, "No labels matched the CEL expressions, aborting NimbusPolicy creation due to missing keys in labels") - return nil, nil + return nil, errors.Wrap(err, "No labels matched the CEL expressions, aborting NimbusPolicy creation due to missing keys in labels") } - // Creates a NimbusPolicy. nimbusPolicy := &v1.NimbusPolicy{ ObjectMeta: metav1.ObjectMeta{ - Name: binding.Name, - Namespace: binding.Namespace, + Name: sib.Name, + Namespace: sib.Namespace, + Labels: sib.Labels, }, Spec: v1.NimbusPolicySpec{ Selector: v1.NimbusSelector{ MatchLabels: matchLabels, }, - NimbusRules: nimbusRulesList, - }, - Status: v1.NimbusPolicyStatus{ - Status: "Pending", + NimbusRules: nimbusRules, }, } - if err = ctrl.SetControllerReference(binding, nimbusPolicy, scheme); err != nil { - logger.Error(err, "failed to set OwnerReference") - return nil, err + if err = ctrl.SetControllerReference(&sib, nimbusPolicy, scheme); err != nil { + return nil, errors.Wrap(err, "failed to set NimbusPolicy OwnerReference") } - logger.Info("NimbusPolicy built successfully", "Policy", nimbusPolicy) + logger.Info("NimbusPolicy built successfully", "NimbusPolicy.Name", nimbusPolicy.Name, "NimbusPolicy.Namespace", nimbusPolicy.Namespace) return nimbusPolicy, nil } -// fetchBinding fetches a SecurityIntentBinding by its name and namespace. -func fetchBinding(ctx context.Context, client client.Client, name string, namespace string) (*v1.SecurityIntentBinding, error) { - logger := log.FromContext(ctx) - var binding v1.SecurityIntentBinding - if err := client.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, &binding); err != nil { - logger.Error(err, "Failed to get SecurityIntentBinding") - return nil, err - } - return &binding, nil -} - // extractSelector extracts match labels from a Selector. func extractSelector(ctx context.Context, k8sClient client.Client, namespace string, selector v1.Selector) (map[string]string, error) { matchLabels := make(map[string]string) // Initialize map for match labels.