Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code revision #6

Merged
merged 10 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ COPY go.sum go.sum
RUN go mod download

# Copy the go source
COPY cmd/main.go cmd/main.go
COPY main.go main.go
COPY api/ api/
COPY internal/controller/ internal/controller/
COPY controller/ controller/

# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
Expand Down
60 changes: 60 additions & 0 deletions controllers/general/general_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package general

import (
"context"
"fmt"

ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

intentv1 "github.com/5GSEC/nimbus/api/v1"
)

// GeneralController is a struct that holds a Kubernetes client and a WatcherIntent.
type GeneralController struct {
Client client.Client // Client is used to interact with the Kubernetes API.
WatcherIntent *WatcherIntent // WatcherIntent is a custom struct to manage specific operations.
}

// NewGeneralController creates a new instance of GeneralController.
func NewGeneralController(client client.Client) (*GeneralController, error) {
if client == nil {
// If the client is not provided, return an error.
return nil, fmt.Errorf("GeneralController: Client is nil")
}

// Create a new WatcherIntent.
watcherIntent, err := NewWatcherIntent(client)
if err != nil {
// If there is an error in creating WatcherIntent, return an error.
return nil, fmt.Errorf("GeneralController: Error creating WatcherIntent: %v", err)
}

// Return a new GeneralController instance with initialized fields.
return &GeneralController{
Client: client,
WatcherIntent: watcherIntent,
}, nil
}

// Reconcile is the method that will be called when there is an update to the resources being watched.
func (gc *GeneralController) Reconcile(ctx context.Context, req ctrl.Request) (*intentv1.SecurityIntent, error) {
if gc == nil {
// If the GeneralController instance is nil, return an error.
return nil, fmt.Errorf("GeneralController is nil")
}
if gc.WatcherIntent == nil {
// If the WatcherIntent is not set, return an error.
return nil, fmt.Errorf("WatcherIntent is nil")
}

// Call the Reconcile method of WatcherIntent to handle the specific logic.
intent, err := gc.WatcherIntent.Reconcile(ctx, req)
if err != nil {
// If there is an error in reconciliation, return the error.
return nil, fmt.Errorf("Error in WatcherIntent.Reconcile: %v", err)
}

// Return the intent and nil as error if reconciliation is successful.
return intent, nil
}
64 changes: 64 additions & 0 deletions controllers/general/watch_intent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package general

import (
"context"
"fmt"

"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"

intentv1 "github.com/5GSEC/nimbus/api/v1"
"k8s.io/apimachinery/pkg/api/errors"
)

// WatcherIntent is a struct that holds a Kubernetes client.
type WatcherIntent struct {
Client client.Client // Client to interact with Kubernetes resources.
}

// NewWatcherIntent creates a new instance of WatcherIntent.
func NewWatcherIntent(client client.Client) (*WatcherIntent, error) {
if client == nil {
// Return an error if the client is not provided.
return nil, fmt.Errorf("WatcherIntent: Client is nil")
}

// Return a new WatcherIntent instance with the provided client.
return &WatcherIntent{
Client: client,
}, nil
}

// Reconcile is the method that handles the reconciliation of the Kubernetes resources.
func (wi *WatcherIntent) Reconcile(ctx context.Context, req ctrl.Request) (*intentv1.SecurityIntent, error) {
log := log.FromContext(ctx) // Get the logger from the context.

// Check if WatcherIntent or its client is not initialized.
if wi == nil || wi.Client == nil {
fmt.Println("WatcherIntent is nil or Client is nil in Reconcile")
return nil, fmt.Errorf("WatcherIntent or Client is not initialized")
}

intent := &intentv1.SecurityIntent{} // Create an instance of SecurityIntent.
// Attempt to get the SecurityIntent resource from Kubernetes.
err := wi.Client.Get(ctx, types.NamespacedName{
Name: req.Name,
Namespace: req.Namespace,
}, intent)

if err != nil {
// Handle the case where the SecurityIntent resource is not found.
if errors.IsNotFound(err) {
log.Info("SecurityIntent resource not found. Ignoring since object must be deleted")
return nil, nil
}
// Log and return an error if there is a problem getting the SecurityIntent.
log.Error(err, "Failed to get SecurityIntent")
return nil, err
}

// Return the SecurityIntent instance if found successfully.
return intent, nil
}
115 changes: 115 additions & 0 deletions controllers/policy/network_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package policy

import (
"context"

"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"

intentv1 "github.com/5GSEC/nimbus/api/v1"
ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
kubearmorpolicyv1 "github.com/kubearmor/KubeArmor/pkg/KubeArmorPolicy/api/security.kubearmor.com/v1"

utils "github.com/5GSEC/nimbus/controllers/utils"
)

// NetworkPolicyController struct to handle network policies.
type NetworkPolicyController struct {
Client client.Client // Client to interact with Kubernetes API.
Scheme *runtime.Scheme // Scheme defines the runtime scheme of the Kubernetes objects.
}

// NewNetworkPolicyController creates a new instance of NetworkPolicyController.
func NewNetworkPolicyController(client client.Client, scheme *runtime.Scheme) *NetworkPolicyController {
return &NetworkPolicyController{
Client: client,
Scheme: scheme,
}
}

// HandlePolicy processes the network policies defined in the SecurityIntent resource.
func (npc *NetworkPolicyController) HandlePolicy(ctx context.Context, intent *intentv1.SecurityIntent) error {
log := log.FromContext(ctx) // Logger with context.

// Build and apply/update Cilium Network Policy based on SecurityIntent.
ciliumPolicy := utils.BuildCiliumNetworkPolicySpec(ctx, intent).(*ciliumv2.CiliumNetworkPolicy)
err := utils.ApplyOrUpdatePolicy(ctx, npc.Client, ciliumPolicy, ciliumPolicy.Name)
if err != nil {
log.Error(err, "Failed to apply Cilium Network Policy", "Name", ciliumPolicy.Name)
return err
}

// If SecurityIntent contains protocol resources, build and apply/update KubeArmor Network Policy.
if containsProtocolResource(intent) {
armorNetPolicy := utils.BuildKubeArmorPolicySpec(ctx, intent, utils.GetPolicyType(utils.IsHostPolicy(intent))).(*kubearmorpolicyv1.KubeArmorPolicy)
err = utils.ApplyOrUpdatePolicy(ctx, npc.Client, armorNetPolicy, armorNetPolicy.Name)
if err != nil {
log.Error(err, "Failed to apply KubeArmor Network Policy", "Name", armorNetPolicy.Name)
return err
}
}

log.Info("Applied Network Policy", "PolicyName", intent.Name)
return nil
}

// DeletePolicy removes the network policy associated with the SecurityIntent resource.
func (npc *NetworkPolicyController) DeletePolicy(ctx context.Context, intent *intentv1.SecurityIntent) error {
log := log.FromContext(ctx)
var err error

// Delete KubeArmor or Cilium Network Policy based on the contents of SecurityIntent.

if containsProtocolResource(intent) {
err = deleteNetworkPolicy(ctx, npc.Client, "KubeArmorPolicy", intent.Name, intent.Namespace)
if err != nil {
log.Error(err, "Failed to delete KubeArmor Network Policy", "Name", intent.Name)
return err
}
} else {
// Delete Cilium Network Policy by default
err = deleteNetworkPolicy(ctx, npc.Client, "CiliumNetworkPolicy", intent.Name, intent.Namespace)
if err != nil {
log.Error(err, "Failed to delete Cilium Network Policy", "Name", intent.Name)
return err
}
}

log.Info("Deleted Network Policy", "PolicyName", intent.Name)
return nil
}

// Additional helper functions for policy creation and deletion.
func createCiliumNetworkPolicy(ctx context.Context, intent *intentv1.SecurityIntent) *ciliumv2.CiliumNetworkPolicy {
return utils.BuildCiliumNetworkPolicySpec(ctx, intent).(*ciliumv2.CiliumNetworkPolicy)
}

func createKubeArmorNetworkPolicy(ctx context.Context, intent *intentv1.SecurityIntent) *kubearmorpolicyv1.KubeArmorPolicy {
return utils.BuildKubeArmorPolicySpec(ctx, intent, "policy").(*kubearmorpolicyv1.KubeArmorPolicy)
}

func applyCiliumNetworkPolicy(ctx context.Context, c client.Client, policy *ciliumv2.CiliumNetworkPolicy) {
utils.ApplyOrUpdatePolicy(ctx, c, policy, policy.Name)
}

func applyKubeArmorNetworkPolicy(ctx context.Context, c client.Client, policy *kubearmorpolicyv1.KubeArmorPolicy) {
utils.ApplyOrUpdatePolicy(ctx, c, policy, policy.Name)
}

// containsProtocolResource checks for the presence of protocol resources in SecurityIntent.
func containsProtocolResource(intent *intentv1.SecurityIntent) bool {
// Iterates through the intent resources to find if 'protocols' key is present.
for _, resource := range intent.Spec.Intent.Resource {
if resource.Key == "protocols" {
return true
}
}
return false
}

// deleteNetworkPolicy helps in deleting a specified network policy.
func deleteNetworkPolicy(ctx context.Context, c client.Client, policyType, name, namespace string) error {
// Utilizes utility function to delete the specified network policy.
return utils.DeletePolicy(ctx, c, policyType, name, namespace)
}
90 changes: 90 additions & 0 deletions controllers/policy/policy_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package policy

import (
"context"
"fmt"

"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"

intentv1 "github.com/5GSEC/nimbus/api/v1"
utils "github.com/5GSEC/nimbus/controllers/utils"
)

// Constant for the finalizer name used in the SecurityIntent resource.
const securityIntentFinalizer = "finalizer.securityintent.intent.security.nimbus.com"

// PolicyController struct handles different types of policies.
type PolicyController struct {
Client client.Client // Client for interacting with Kubernetes API.
Scheme *runtime.Scheme // Scheme defines the runtime scheme of the Kubernetes objects.
SystemPolicyController *SystemPolicyController // Controller for handling system policies.
NetworkPolicyController *NetworkPolicyController // Controller for handling network policies.
}

// NewPolicyController creates a new instance of PolicyController.
func NewPolicyController(client client.Client, scheme *runtime.Scheme) *PolicyController {
if client == nil || scheme == nil {
// Print an error and return nil if the client or scheme is not provided.
fmt.Println("PolicyController: Client or Scheme is nil")
return nil
}

// Initialize and return a new PolicyController with system and network policy controllers.
return &PolicyController{
Client: client,
Scheme: scheme,
SystemPolicyController: NewSystemPolicyController(client, scheme),
NetworkPolicyController: NewNetworkPolicyController(client, scheme),
}
}

// Reconcile handles the reconciliation logic for the SecurityIntent resource.
func (pc *PolicyController) Reconcile(ctx context.Context, intent *intentv1.SecurityIntent) error {
log := log.FromContext(ctx) // Logger with context.
log.Info("Processing policy", "Name", intent.Name, "Type", intent.Spec.Intent.Type)

var err error

// Switch-case to handle different types of policies based on the intent type.
switch intent.Spec.Intent.Type {
case "system":
log.Info("Handling system policy")
err = pc.SystemPolicyController.HandlePolicy(ctx, intent)
case "network":
log.Info("Handling network policy")
err = pc.NetworkPolicyController.HandlePolicy(ctx, intent)
default:
log.Info("Unknown policy type", "Type", intent.Spec.Intent.Type)
}

// Handling finalizer logic for clean up during delete operations.
if intent.ObjectMeta.DeletionTimestamp.IsZero() {
// If the resource is not being deleted, add the finalizer if it's not present.
if !utils.ContainsString(intent.ObjectMeta.Finalizers, securityIntentFinalizer) {
intent.ObjectMeta.Finalizers = append(intent.ObjectMeta.Finalizers, securityIntentFinalizer)
err = pc.Client.Update(ctx, intent)
}
} else {
// If the resource is being deleted, process deletion based on policy type and remove finalizer.
if utils.ContainsString(intent.ObjectMeta.Finalizers, securityIntentFinalizer) {
switch intent.Spec.Intent.Type {
case "system":
err = pc.SystemPolicyController.DeletePolicy(ctx, intent)
case "network":
err = pc.NetworkPolicyController.DeletePolicy(ctx, intent)
default:
err = fmt.Errorf("unknown policy type: %s", intent.Spec.Intent.Type)
}

// Removing the finalizer after handling deletion.
intent.ObjectMeta.Finalizers = utils.RemoveString(intent.ObjectMeta.Finalizers, securityIntentFinalizer)
if updateErr := pc.Client.Update(ctx, intent); updateErr != nil {
return updateErr
}
}
}

return err
}
Loading