diff --git a/nimbus-operator/exporter/httpexporter/http_nimbus.go b/nimbus-operator/exporter/httpexporter/http_nimbus.go new file mode 100644 index 00000000..a1563348 --- /dev/null +++ b/nimbus-operator/exporter/httpexporter/http_nimbus.go @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Authors of Nimbus + +// Initialize HTTP Client: Set up the client for HTTP communication. +// Format Nimbus Policy Data: Convert Nimbus Policy data into JSON format. +// Send Data: Send the converted data to the adapter's URL using a POST request. +// Process Response: Handle the response from the adapter and log as necessary. + +package httpexporter + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + + v1 "github.com/5GSEC/nimbus/nimbus-operator/api/v1" +) + +// HttpNimbusExporter struct defines the HTTP client and the URL for exporting Nimbus policies. +type HttpNimbusExporter struct { + client *http.Client + url string +} + +// NewHttpNimbusExporter creates a new HttpNimbusExporter with the provided URL. +func NewHttpNimbusExporter(url string) *HttpNimbusExporter { + return &HttpNimbusExporter{ + client: &http.Client{}, + url: url, + } +} + +// ExportNimbusPolicy exports a NimbusPolicy to a remote server via HTTP POST. +func (h *HttpNimbusExporter) ExportNimbusPolicy(ctx context.Context, policy *v1.NimbusPolicy) error { + // Convert the NimbusPolicy into JSON format. + data, err := json.Marshal(policy) + if err != nil { + return fmt.Errorf("failed to marshal NimbusPolicy: %v", err) + } + + // Create a new HTTP POST request with the policy data. + req, err := http.NewRequestWithContext(ctx, "POST", h.url, bytes.NewBuffer(data)) + if err != nil { + return fmt.Errorf("failed to create request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + + // Send the request to the server. + resp, err := h.client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + // Check if the response status is OK (HTTP 200). + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("non-OK response received: %v", resp.Status) + } + + return nil +} diff --git a/nimbus-operator/exporter/nimbuspolicy/nimbuspolicy_controller.go b/nimbus-operator/exporter/nimbuspolicy/nimbuspolicy_controller.go new file mode 100644 index 00000000..1ffc97a0 --- /dev/null +++ b/nimbus-operator/exporter/nimbuspolicy/nimbuspolicy_controller.go @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Authors of Nimbus + +package nimbuspolicy + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + 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/nimbus-operator/api/v1" + "github.com/5GSEC/nimbus/nimbus-operator/exporter/httpexporter" + watcher "github.com/5GSEC/nimbus/nimbus-operator/receiver/watcher" +) + +// NimbusPolicyReconciler reconciles a NimbusPolicy object. +type NimbusPolicyReconciler struct { + client.Client + Scheme *runtime.Scheme + WatcherNimbusPolicy *watcher.WatcherNimbusPolicy +} + +// NewNimbusPolicyReconciler creates a new instance of NimbusPolicyReconciler. +// It initializes the WatcherNimbusPolicy which watches and reacts to changes in NimbusPolicy objects. +func NewNimbusPolicyReconciler(client client.Client, scheme *runtime.Scheme) *NimbusPolicyReconciler { + if client == nil { + fmt.Println("NimbusPolicyReconciler: Client is nil") + return nil + } + + watcherNimbusPolicy, err := watcher.NewWatcherNimbusPolicy(client) + if err != nil { + fmt.Println("NimbusPolicyReconciler: Failed to initialize WatcherNimbusPolicy:", err) + return nil + } + + return &NimbusPolicyReconciler{ + Client: client, + Scheme: scheme, + WatcherNimbusPolicy: watcherNimbusPolicy, + } +} + +//+kubebuilder:rbac:groups=intent.security.nimbus.com,resources=nimbuspolicies,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=intent.security.nimbus.com,resources=nimbuspolicies/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=intent.security.nimbus.com,resources=nimbuspolicies/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the NimbusPolicy object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile +func (r *NimbusPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + if r.WatcherNimbusPolicy == nil { + fmt.Println("NimbusPolicyReconciler: WatcherNimbusPolicy is nil") + return ctrl.Result{}, fmt.Errorf("WatcherNimbusPolicy is not properly initialized") + } + + nimPol, err := r.WatcherNimbusPolicy.Reconcile(ctx, req) + if err != nil { + log.Error(err, "Error in WatcherNimbusPolicy.Reconcile", "Request", req.NamespacedName) + return ctrl.Result{}, err + } + + if nimPol != nil { + log.Info("NimbusPolicy resource found", "Name", req.Name, "Namespace", req.Namespace) + } else { + log.Info("NimbusPolicy resource not found", "Name", req.Name, "Namespace", req.Namespace) + } + + // Exporting the NimbusPolicy if it is found. + if nimPol != nil { + exporter := httpexporter.NewHttpNimbusExporter("http://localhost:13000/api/v1/nimbus/export") // Update the URL as needed. + err := exporter.ExportNimbusPolicy(ctx, nimPol) + if err != nil { + log.Error(err, "Failed to export NimbusPolicy") + return ctrl.Result{}, err + } + log.Info("NimbusPolicy exported successfully") + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +// It registers the NimbusPolicyReconciler to manage NimbusPolicy resources. +func (r *NimbusPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1.NimbusPolicy{}). + Complete(r) +} diff --git a/nimbus-operator/exporter/nimbuspolicy/suite_test.go b/nimbus-operator/exporter/nimbuspolicy/suite_test.go new file mode 100644 index 00000000..9bd5f29f --- /dev/null +++ b/nimbus-operator/exporter/nimbuspolicy/suite_test.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Authors of Nimbus + +package nimbuspolicy + +import ( + "fmt" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + intentv1 "github.com/5GSEC/nimbus/api/v1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", + fmt.Sprintf("1.28.3-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = intentv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +})