diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..93ea439 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '"[Bug] "' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment (please complete the following information):** + - OS: [e.g. Ubuntu 22.04] + - Numbat Versions: [e.g. v0.1] + - Kubernetes Environment: [tip: Please include CRI and CNI as well as their versions] + - Istio Environment: [tip: Please include Istio version as well as the install profiles] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..9cde18a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[REQUEST]" +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/ci-test-go.yml b/.github/workflows/ci-test-go.yml new file mode 100644 index 0000000..b27c53e --- /dev/null +++ b/.github/workflows/ci-test-go.yml @@ -0,0 +1,56 @@ +name: ci-test-go +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + go-fmt-sentryflow: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: "v1.19.1" + + - name: Check go-fmt + run: make gofmt + working-directory: sentryflow + + go-lint-sentryflow: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: "v1.19.1" + + - name: Check Golint + run: make golint + working-directory: sentryflow + + go-sec-sentryflow: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: "v1.19.1" + + - name: Run Gosec Security Scanner + run: make gosec + working-directory: sentryflow + + license: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + + - name: Check License Header + uses: apache/skywalking-eyes@a790ab8dd23a7f861c18bd6aaa9b012e3a234bce + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sentryflow-pr-checks.yml b/.github/workflows/sentryflow-pr-checks.yml new file mode 100644 index 0000000..16c5f39 --- /dev/null +++ b/.github/workflows/sentryflow-pr-checks.yml @@ -0,0 +1,30 @@ +name: sentryflow-pr-checks + +on: + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup Docker Build + uses: docker/setup-buildx-action@v1 + + - name: Get tag + id: tag + run: | + if [ ${{ github.ref }} == "refs/heads/main" ]; then + echo "tag=latest" >> $GITHUB_OUTPUT + else + echo "tag=tmp" >> $GITHUB_OUTPUT + fi + + - name: Build Docker Image + working-directory: ./sentryflow + run: | + make TAG=${{ steps.tag.outputs.tag }} image diff --git a/.github/workflows/sentryflow-release-image.yml b/.github/workflows/sentryflow-release-image.yml new file mode 100644 index 0000000..a32559c --- /dev/null +++ b/.github/workflows/sentryflow-release-image.yml @@ -0,0 +1,40 @@ +name: sentryflow-release-image + +on: + push: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Setup Docker Build + uses: docker/setup-buildx-action@v1 + + # - name: Login to DockerHub + # uses: docker/login-action@v1 + # with: + # username: ${{ secrets.DOCKERHUB_USERNAME }} + # password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Get tag + id: tag + run: | + if [ ${{ github.ref }} == "refs/heads/main" ]; then + echo "tag=latest" >> $GITHUB_OUTPUT + else + echo "tag=tmp" >> $GITHUB_OUTPUT + fi + + - name: Build Docker Image + working-directory: ./sentryflow + run: | + make TAG=${{ steps.tag.outputs.tag }} image + + # - name: Push Docker Image + # run: | + # docker push boanlab/sentryflow:${{ steps.tag.outputs.tag }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/.licenserc.yaml b/.licenserc.yaml new file mode 100644 index 0000000..b683404 --- /dev/null +++ b/.licenserc.yaml @@ -0,0 +1,20 @@ +header: + license: + spdx-id: Apache-2.0 + copyright-owner: SentryFlow + content: | + SPDX-License-Identifier: Apache-2.0 + + paths: + - "**/*.go" + - "**/Dockerfile" + - "**/Makefile" + + paths-ignore: + - "protobuf/*" + + comment: on-failure + +dependency: + files: + - go.mod diff --git a/README.md b/README.md index 7c7b460..bc7ee81 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ + # SentryFlow -API Observability and Security + +[![SentryFlow Docker Build](https://github.com/5GSEC/sentryflow/actions/workflows/sentryflow-release-image.yml/badge.svg)](https://github.com/5GSEC/sentryflow/actions/workflows/sentryflow-release-image.yml) [![CI Test](https://github.com/5GSEC/sentryflow/actions/workflows/ci-test-go.yml/badge.svg)](https://github.com/5GSEC/sentryflow/actions/workflows/ci-test-go.yml) + +SentryFlow is a cloud-native system for API observability and security, specializing in log collection, metric production, and data exportation. + +## Architecture Overview + +![Sentryflow Overview](docs/sentryflow_overview.png) + +### Features +- Generation of API Access Logs +- Proudction of API Metrics and Statistics +- Inference of API Specifications + +## Documentation + +### Basic Information +- [Getting Started](docs/getting_started.md) +- [Use Cases](examples/README.md) + +### Contribution +- [Contribution Guide](contribution/README.md) diff --git a/contribution/README.md b/contribution/README.md new file mode 100644 index 0000000..605a1c7 --- /dev/null +++ b/contribution/README.md @@ -0,0 +1,88 @@ +# Development Guide + +SentryFlow operates within an Istio environment on Kubernetes, indicating that contributors to our project will need to have an Istio environment set up. + +To minimize the hassle of installing and uninstalling Kubernetes and configuring Istio solely for our project, we have provided a straightforward Vagrantfile. This Vagrantfile initializes an Ubuntu virtual machine equipped with a fully operational Kubernetes and Istio environment. + +## 1. Prerequisites + +We employ Vagrant to provision VirtualBox virtual machines, creating a Kubernetes environment. As such, it is highly recommended to install the following package versions in your local environment: + +- **[Vagrant](https://www.vagrantup.com/)** - v2.2.9 +- **[VirtualBox](https://www.virtualbox.org/)** - v6.1 + +## 2. Starting up a VM + +We have configured a Vagrantfile that initiates an Ubuntu 22.04 machine with Kubernetes pre-installed. The setup for Kubernetes is as described below: + +> **Note:** Although Kubernetes officially advises the use of containerd over Docker as the Container Runtime Interface (CRI), we have chosen to use Docker as the CRI within our Kubernetes setup. This decision facilitates the building and testing of SentryFlow and its client images. + +- Kubernetes: 1.23 +- [CRI] Docker: 24.0.7 +- [CNI] Calico: 0.3.1 + +To proceed, execute the following command within the `contribution/` directory: + +```bash +$ vagrant up +Bringing machine 'sentryflow' up with 'virtualbox' provider... +==> sentryflow: Importing base box 'generic/ubuntu2204'... +==> sentryflow: Matching MAC address for NAT networking... +==> sentryflow: Checking if box 'generic/ubuntu2204' version '4.3.10' is up to date... +... + sentryflow: clusterrolebinding.rbac.authorization.k8s.io/calico-node created + sentryflow: clusterrolebinding.rbac.authorization.k8s.io/calico-cni-plugin created + sentryflow: daemonset.apps/calico-node created + sentryflow: deployment.apps/calico-kube-controllers created +``` + +This command will initiate the installation of the necessary development environment. The duration of this process may vary, primarily depending on the speed of your network connection, and could take several minutes to complete. + +## 3. Development and Code Quality + +### Development + +After Vagrant has been successfully initialized, you can access the Istio and Kubernetes environment by executing the following steps: + +``` +$ vagrant ssh +``` + +The source code for SentryFlow will be located in `/home/vagrant/sentryflow` within the virtual environment, and this directory will also be synchronized with the current work directory on the host machine. + +After making modifications to the source code of SentryFlow, you can build the changes by moving to the `sentryflow` directory and running the Makefile. + +``` +make build +``` + +Executing the Makefile will result in the construction of container images, each tagged as specified. + +### Code Quality + +To maintain a clean and secure code base for SentryFlow, we conduct several checks, including `gofmt` for code formatting, `golint` for code style and linting, and `gosec` for security scanning. + +To evaluate the quality of your code, navigate to the `sentryflow` directory and execute the following commands: + +``` +make golint # will run golint checks +make gofmt # will run gofmt checks +make gosec # will run gosec checks +``` + +### Pull Request + +Once everything is correctly set up, you are ready to create a pull request. Please refer to our guidelines for submitting PRs. + +## 4. Cleaning Up + +If you have successfully made changes to SentryFlow and wish to clean up the created workspace, you can simply use the following command: + +``` +$ vagrant destroy + sentryflow: Are you sure you want to destroy the 'sentryflow' VM? [y/N] y +==> sentryflow: Forcing shutdown of VM... +==> sentryflow: Destroying VM and associated drives... +``` + +Executing the command will terminate and remove the VM that you were working on. diff --git a/contribution/vagrant/.gitignore b/contribution/vagrant/.gitignore new file mode 100644 index 0000000..a977916 --- /dev/null +++ b/contribution/vagrant/.gitignore @@ -0,0 +1 @@ +.vagrant/ diff --git a/contribution/vagrant/Vagrantfile b/contribution/vagrant/Vagrantfile new file mode 100644 index 0000000..a4e51a8 --- /dev/null +++ b/contribution/vagrant/Vagrantfile @@ -0,0 +1,51 @@ +Vagrant.require_version ">= 2.0.0" + +VM_NAME = "sentryflow" +IMG_NAME = "generic/ubuntu2204" + +NUM_OF_VCPUS = 4 +SIZE_OF_VMEM = 4096 + +## == ## + +# create ssh keys if needed +system(" + if [ #{ARGV[0]} = 'up' ]; then + if [ ! -f ~/.ssh/id_rsa ]; then + echo '~/.ssh/id_rsa keys does not exist.' + ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa + fi + fi +") + +## == ## + +Vagrant.configure("2") do |config| + # vagrant@VM_NAME + config.vm.hostname = VM_NAME + + config.vm.define VM_NAME do |cfg| + cfg.vm.box = IMG_NAME + + cfg.vm.provider "virtualbox" do |vb| + vb.memory = SIZE_OF_VMEM + vb.cpus = NUM_OF_VCPUS + end + end + + # sync directories + config.vm.synced_folder "../../", "/home/vagrant/sentryflow", owner:"vagrant", group:"vagrant" + + # configure SSH + config.ssh.insert_key = false + + # copy ssh keys + config.vm.provision "file", source: "~/.ssh/id_rsa.pub", destination: "/home/vagrant/.ssh/id_rsa.pub" + config.vm.provision :shell, :inline => "cat /home/vagrant/.ssh/id_rsa.pub >> /home/vagrant/.ssh/authorized_keys", run: "always" + + # copy git config + config.vm.provision :file, source: "~/.gitconfig", destination: "$HOME/.gitconfig" + + # setup k8s and IStio + config.vm.provision "shell", path: "setup.sh" +end diff --git a/contribution/vagrant/install-scripts/install-kvm.sh b/contribution/vagrant/install-scripts/install-kvm.sh new file mode 100755 index 0000000..2ba5307 --- /dev/null +++ b/contribution/vagrant/install-scripts/install-kvm.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +. /etc/os-release + +if [ "$NAME" != "Ubuntu" ]; then + echo "This script is for Ubuntu." + exit +fi + +# install kvm and dependencies +sudo apt-get install -y bridge-utils libguestfs-tools \ + libvirt-daemon-system libvirt-clients libvirt-daemon libvirt-dev \ + qemu-system qemu-kvm virt-manager diff --git a/contribution/vagrant/install-scripts/install-vagrant-libvirt.sh b/contribution/vagrant/install-scripts/install-vagrant-libvirt.sh new file mode 100755 index 0000000..3678258 --- /dev/null +++ b/contribution/vagrant/install-scripts/install-vagrant-libvirt.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +. /etc/os-release + +if [ "$NAME" != "Ubuntu" ]; then + echo "This script is for Ubuntu." + exit +fi + +if [ ! -x "$(command -v vagrant)" ]; then + echo "Please install Vagrant first." +else + # update repo + sudo apt-get update + + # install build-essential + sudo apt-get install -y build-essential + + # install vagrant-libvirt + vagrant plugin install vagrant-libvirt +fi diff --git a/contribution/vagrant/install-scripts/install-vagrant.sh b/contribution/vagrant/install-scripts/install-vagrant.sh new file mode 100755 index 0000000..899bf0b --- /dev/null +++ b/contribution/vagrant/install-scripts/install-vagrant.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +. /etc/os-release + +if [ "$NAME" != "Ubuntu" ]; then + echo "This script is for Ubuntu." + exit +fi + +if [ ! -x "$(command -v vagrant)" ]; then + VAGRANT_VERSION=2.3.0 + + # install wget + sudo apt-get -y install wget + + # download vagrant package + wget https://releases.hashicorp.com/vagrant/$VAGRANT_VERSION/vagrant_$VAGRANT_VERSION-1_amd64.deb + + # install vagrant + sudo apt-get -y install ./vagrant_$VAGRANT_VERSION-1_amd64.deb + + # rm the vagrant package + rm vagrant_$VAGRANT_VERSION-1_amd64.deb + + # install vagrant plugins + vagrant plugin install vagrant-vbguest + vagrant plugin install vagrant-reload +else + echo "Found Vagrant, skipping the installation of Vagrant." +fi diff --git a/contribution/vagrant/install-scripts/install-virtualbox.sh b/contribution/vagrant/install-scripts/install-virtualbox.sh new file mode 100755 index 0000000..b7768ca --- /dev/null +++ b/contribution/vagrant/install-scripts/install-virtualbox.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +. /etc/os-release + +if [ "$NAME" != "Ubuntu" ]; then + echo "This script is for Ubuntu." + exit +fi + +if [ ! -x "$(command -v vboxmanage)" ]; then + # install wget + sudo apt-get -y install wget + + # download oracle_vbox_2016.asc and register it to the system + wget -O- https://www.virtualbox.org/download/oracle_vbox_2016.asc | sudo gpg --dearmor --yes --output /usr/share/keyrings/oracle-virtualbox-2016.gpg + + # install vbox + sudo apt-get update + sudo apt-get install virtualbox-6.1 + + echo "Please reboot the machine." +else + echo "Found VirtualBox, skipping the installation of Virtualbox." +fi diff --git a/contribution/vagrant/setup.sh b/contribution/vagrant/setup.sh new file mode 100755 index 0000000..da111f7 --- /dev/null +++ b/contribution/vagrant/setup.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# From BoanLab's tools scripts +git clone https://github.com/boanlab/tools.git + +# Install Docker +bash tools/containers/install-docker.sh + +# Install Kubeadm +bash tools/kubernetes/install-kubeadm.sh + +# Disable Swap +sudo swapoff -a + +# Initialize Kubernetes for single node +export MULTI=false +bash tools/kubernetes/initialize-kubeadm.sh + +# Deploy Calico +export CNI=calico +bash tools/kubernetes/deploy-cni.sh + +# Make kubectl related commands accessable for vagrant user +sudo mkdir -p /home/vagrant/.kube +sudo cp -i /etc/kubernetes/admin.conf /home/vagrant/.kube/config +sudo chown $(id -u vagrant):$(id -g vagrant) /home/vagrant/.kube/config + +# Now install Istio +sudo apt-get install make +curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.20.3 sh - +export PATH="$PATH:/home/vagrant/istio-1.20.3/bin" +istioctl install --set profile=default -y +sudo chown -R vagrant /home/vagrant/istio-1.20.3/ + +# Now install golang, this is for golint, gosec, gofmt +wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz +sudo rm -rf /usr/local/go +sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz +export PATH=$PATH:/usr/local/go/bin + +# Setup bashrc +echo export GOPATH="/home/vagrant/go" >> /home/vagrant/.bashrc +echo export PATH="$PATH:/usr/local/go/bin:/home/vagrant/istio-1.20.3/bin:/home/vagrant/go/bin/" >> /home/vagrant/.bashrc diff --git a/deployments/log-client.yaml b/deployments/log-client.yaml new file mode 100644 index 0000000..3732dc5 --- /dev/null +++ b/deployments/log-client.yaml @@ -0,0 +1,27 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: log-client + namespace: sentryflow +spec: + replicas: 1 + selector: + matchLabels: + app: log-client + template: + metadata: + labels: + app: log-client + spec: + containers: + - name: log-client + image: 5gsec/sentryflow-log-client:v0.1 + ports: + - containerPort: 8080 + protocol: TCP + name: grpc + env: + - name: SERVER_ADDR + value: "sentryflow.sentryflow.svc.cluster.local" + - name: SERVER_PORT + value: "8080" diff --git a/deployments/mongo-client.yaml b/deployments/mongo-client.yaml new file mode 100644 index 0000000..5fa9079 --- /dev/null +++ b/deployments/mongo-client.yaml @@ -0,0 +1,64 @@ +# Setup deployment for mongodb +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongodb + namespace: sentryflow +spec: + replicas: 1 + selector: + matchLabels: + app: mongodb + template: + metadata: + labels: + app: mongodb + spec: + containers: + - name: mongodb + image: mongo:latest + ports: + - containerPort: 27017 +--- +# Setup service for mongodb +apiVersion: v1 +kind: Service +metadata: + name: mongodb + namespace: sentryflow +spec: + selector: + app: mongodb + ports: + - protocol: TCP + port: 27017 + targetPort: 27017 +--- +# Setup deployment for mongo-client +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongo-client + namespace: sentryflow +spec: + replicas: 1 + selector: + matchLabels: + app: mongo-client + template: + metadata: + labels: + app: mongo-client + spec: + imagePullSecrets: + - name: regcred + containers: + - name: mongo-client + image: 5gsec/sentryflow-mongo-client:v0.1 + env: + - name: SERVER_ADDR + value: "sentryflow.sentryflow.svc.cluster.local" + - name: SERVER_PORT + value: "8080" + - name: MONGODB_HOST + value: "mongodb://mongodb:27017" diff --git a/deployments/sentryflow.yaml b/deployments/sentryflow.yaml new file mode 100644 index 0000000..3284d75 --- /dev/null +++ b/deployments/sentryflow.yaml @@ -0,0 +1,82 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: sentryflow + labels: + istio-injection: disabled # avoid Istio sidecar injection + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/warn: privileged +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sa-sentryflow + namespace: sentryflow +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cr-sentryflow +rules: +- apiGroups: ["*"] + verbs: ["*"] + resources: ["*"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: rb-sentryflow +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cr-sentryflow +subjects: +- kind: ServiceAccount + name: sa-sentryflow + namespace: sentryflow +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sentryflow + namespace: sentryflow +spec: + replicas: 1 + selector: + matchLabels: + app: sentryflow + template: + metadata: + labels: + app: sentryflow + spec: + serviceAccountName: sa-sentryflow + containers: + - name: sentryflow + image: 5gsec/sentryflow:v0.0.1 + ports: + - containerPort: 4317 + protocol: TCP + name: grpc-otlp + - containerPort: 8080 + protocol: TCP + name: grpc-export +--- +apiVersion: v1 +kind: Service +metadata: + name: sentryflow + namespace: sentryflow +spec: + selector: + app: sentryflow + ports: + - protocol: TCP + port: 4317 + targetPort: 4317 + name: grpc-otlp + - protocol: TCP + port: 8080 + targetPort: 8080 + name: grpc-export diff --git a/docs/getting_started.md b/docs/getting_started.md new file mode 100644 index 0000000..4945f37 --- /dev/null +++ b/docs/getting_started.md @@ -0,0 +1,94 @@ +# Getting Started + +This guide provides a step-by-step process for deploying SentryFlow on Istio, aimed at enhancing API observability and security. It includes detailed commands for each step along with their explanations. + +> **Note**: SentryFlow is currently in the early stages of development. Please be aware that the information provided here may become outdated or change without notice. + +## 1. Prerequisites + +SentryFlow functions within the Istio framework. Below is a table detailing the environments where SentryFlow has been successfully deployed and verified to be operational. + +|System Name|Version| +|--|--| +|Ubuntu|22.04, 20.04| +|[Istio](https://istio.io/latest/)|1.20.2| +|[Kubernetes](https://kubernetes.io/)|v1.27.1| + +> **Note**: For information on Kubernetes configurations, including Container Network Interface (CNI), Container Runtime Interface (CRI), and their respective runtime settings, please consult the [compatability matrix](k8s_compatibility.md). + +## 2. Deploying SentryFlow + +SentryFlow can be deployed using `kubectl` command. The deployment can be accomplished with the following commands: + +``` +$ git clone https://github.com/5GSEC/sentryflow +$ cd sentryflow/ +$ kubectl create -f deployments/sentryflow.yaml +namespace/sentryflow created +serviceaccount/sa-sentryflow created +clusterrole.rbac.authorization.k8s.io/cr-sentryflow created +clusterrolebinding.rbac.authorization.k8s.io/rb-sentyflow created +deployment.apps/sentryflow created +service/sentryflow created +``` + +This process will create a namespace named `sentryflow` and will establish the necessary Kubernetes resources. + +> **Note**: SentryFlow will automatically modify Istio's `meshConfig` to configure `extensionProviders`, facilitating SentryFlow's API log collection. + +Then, check if SentryFlow is up and running by: + +``` +$ kubectl get pods -n sentryflow +NAME READY STATUS RESTARTS AGE +sentryflow-cd95d79b4-9q7d7 1/1 Running 0 4m41s +``` + +## 3. Deploying SentryFlow Clients + +SentryFlow has now been established within the cluster. In addition, SentryFlow exports API logs and metrics through gRPC. For further details on how this data is transmitted, please consult the [SentryFlow Client Guide](sentryflow_client_guide.md). + +For testing purposes, two simple clients have been developed. + +- `log-client`: Simply log everything coming from SentryFlow service +- `mongo-client`: Stores every logs coming from SentryFlow service to a MongoDB service. + +These clients can be deployed into the cluster under namespace `sentryflow` by following the command: + +- `log-client` + ``` + $ kubectl create -f deployments/log-client.yaml + deployment.apps/log-client created + ``` + +- `mongo-client` + ``` + $ kubectl create -f deployments/mongo-client.yaml + deployment.apps/mongodb created + service/mongodb created + deployment.apps/mongo-client created + ``` + +Then, check if those clients and MongoDB are properly up and running by: + +``` +$ kubectl get pods -n sentryflow +NAME READY STATUS RESTARTS AGE +log-client-6c8864655f-h2sdv 1/1 Running 0 5m28s +mongo-client-7cbf6b888f-vd69g 1/1 Running 0 5m28s +mongodb-6f5d9fc599-zwnxj 1/1 Running 0 5m28s +... +``` + +If you observe `log-client`, `mongo-client`, and `mongodb` running within the namespace, the setup has been completed successfully. + +## 3. Use Cases and Examples + +Up to this point, SentryFlow has been successfully integrated into the Istio service mesh and Kubernetes cluster. For additional details on use cases and examples, please consult the accompanying documentation. + +The links below are organized by their level of complexity, starting from basic and progressing to more complex. + +- [Single HTTP Requests](../examples/httpbin/README.md) +- [RobotShop Demo Microservice](../examples/robotshop/README.md) +- [Nephio Free5gc Workload](../examples/nephio/free5gc/README.md) +- [Nephio OAI Workload](../examples/nephio/oai/README.md) diff --git a/docs/k8s_compatibility.md b/docs/k8s_compatibility.md new file mode 100644 index 0000000..1830762 --- /dev/null +++ b/docs/k8s_compatibility.md @@ -0,0 +1,8 @@ +# Kubernetes Compatability Matrix + +This document outlines various Kubernetes configurations and their compatibility with SentryFlow. + +|OS|Kubernetes Version|CRI|CNI| +|--|--|--|--| +|Ubuntu 20.04|1.27.1|containerd=1.6.19|kindnet=0.4.0| +|Ubuntu 22.04|1.23.0|docker=25.0.3|calico=0.3.1| diff --git a/docs/sentryflow_client_guide.md b/docs/sentryflow_client_guide.md new file mode 100644 index 0000000..890636e --- /dev/null +++ b/docs/sentryflow_client_guide.md @@ -0,0 +1,3 @@ +# SentryFlow Client Guide + +- TBF diff --git a/docs/sentryflow_overview.png b/docs/sentryflow_overview.png new file mode 100644 index 0000000..cbfdb76 Binary files /dev/null and b/docs/sentryflow_overview.png differ diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..5e0978e --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +The links below are organized by their level of complexity, starting from basic and progressing to more complex. + +- [Single HTTP Requests](httpbin/README.md) +- [RobotShop Demo Microservice](robotshop/README.md) +- [Nephio Free5gc Workload](nephio/free5gc/README.md) +- [Nephio OAI Workload](nephio/oai/README.md) diff --git a/examples/bookinfo/README.md b/examples/bookinfo/README.md new file mode 100644 index 0000000..4e78fa3 --- /dev/null +++ b/examples/bookinfo/README.md @@ -0,0 +1,13 @@ +# Example - bookinfo + +## Installation + +https://istio.io/latest/docs/examples/bookinfo/ + +## Namespace Telemetry + +```kubectl create -f telemetry.yaml``` + +## Api request Generation + +```curl http://bookinfo_Address:9080/productpage``` diff --git a/examples/bookinfo/telemetry.yaml b/examples/bookinfo/telemetry.yaml new file mode 100644 index 0000000..815fed0 --- /dev/null +++ b/examples/bookinfo/telemetry.yaml @@ -0,0 +1,9 @@ +apiVersion: telemetry.istio.io/v1alpha1 +kind: Telemetry +metadata: + name: bookinfo-logging + namespace: bookinfo +spec: + accessLogging: + - providers: + - name: sentryflow diff --git a/examples/httpbin/README.md b/examples/httpbin/README.md new file mode 100644 index 0000000..780360d --- /dev/null +++ b/examples/httpbin/README.md @@ -0,0 +1,108 @@ +# Single HTTP Requests + +This document showcases how SentryFlow is capable of capturing API logs for straightforward HTTP requests. The demonstration employs Istio's `sleep` and `httpbin` examples for illustration. + +It is essential to ensure that the `sleep` and `httpbin` deployments are correctly configured and that the default namespace has [Istio injection enabled](https://istio.io/latest/docs/setup/additional-setup/sidecar-injection/#automatic-sidecar-injection) for the setup to function properly. + +## Step 1. Verify Services + +To confirm that Istio is set up correctly, start by verifying if the `default` namespace has Istio injection enabled. This can be done using the following command: + +``` +$ kubectl describe namespace default +Name: default +Labels: istio-injection=enabled +... +``` + +If the namespace `default` has label `istio-injection=enabled`, this was set properly. Now, apply the `telemetry.yaml` in this directory by following command: + +``` +$ kubectl create -f telemetry.yaml +telemetry.telemetry.istio.io/sleep-logging created +``` + +Executing this command will configure `telemetry` for Istio, instructing Envoy proxies to forward access logs to SentryFlow. + +> **Note**: Configuring telemetry could require some time to be fully implemented throughout the entire cluster. + +To ensure that the pods in the `default` namespace are operational, execute the following command: + +``` +$ kubectl get pods -n default +NAME READY STATUS RESTARTS AGE +httpbin-545f698b64-ncvq9 2/2 Running 0 44s +sleep-75bbc86479-fmf4p 2/2 Running 0 35s +``` + +## Step 2. Sending API Requests + +Going forward, the `sleep` pod will initiate API requests to the `httpbin` service, which can be done using the following command: + +``` +$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name}) +$ kubectl exec "$SOURCE_POD" -c sleep -- curl -sS -v httpbin:8000/status/418 +``` + +## Step 3. Checking Logs + +There are two methods of checking logs with SentryFlow clients. + +### 1. Logger + +To examine the logs exported by SentryFlow, you can use the following command: + +``` +$ kubectl logs -n sentryflow -l app=log-client +2024/02/14 17:03:37 [gRPC] Successfully connected to sentryflow.sentryflow.svc.cluster.local:8080 +2024/02/14 17:40:28 [Client] Received log: timeStamp:"[2024-02-14T17:40:27.225Z]" id:1707929670787152 srcNamespace:"default" srcName:"sleep-75bbc86479-fmf4p" srcLabel:{key:"app" value:"sleep"} srcLabel:{key:"pod-template-hash" value:"75bbc86479"} srcLabel:{key:"security.istio.io/tlsMode" value:"istio"} srcLabel:{key:"service.istio.io/canonical-name" value:"sleep"} srcLabel:{key:"service.istio.io/canonical-revision" value:"latest"} srcIP:"10.244.140.11" srcPort:"44126" srcType:"Pod" dstNamespace:"default" dstName:"httpbin" dstLabel:{key:"app" value:"httpbin"} dstLabel:{key:"service" value:"httpbin"} dstIP:"10.105.103.198" dstPort:"8000" dstType:"Service" protocol:"HTTP/1.1" method:"GET" path:"/status/418" responseCode:418 +2024/02/14 17:40:29 [Client] Received log: timeStamp:"[2024-02-14T17:40:28.845Z]" id:1707929670787154 srcNamespace:"default" srcName:"sleep-75bbc86479-fmf4p" srcLabel:{key:"app" value:"sleep"} srcLabel:{key:"pod-template-hash" value:"75bbc86479"} srcLabel:{key:"security.istio.io/tlsMode" value:"istio"} srcLabel:{key:"service.istio.io/canonical-name" value:"sleep"} srcLabel:{key:"service.istio.io/canonical-revision" value:"latest"} srcIP:"10.244.140.11" srcPort:"44158" srcType:"Pod" dstNamespace:"default" dstName:"httpbin" dstLabel:{key:"app" value:"httpbin"} dstLabel:{key:"service" value:"httpbin"} dstIP:"10.105.103.198" dstPort:"8000" dstType:"Service" protocol:"HTTP/1.1" method:"GET" path:"/status/418" responseCode:418 +``` + +As anticipated, we should be able to observe the `/status/418` API request being made from the `sleep` pod to the `httpbin` service. + +### 2. MongoDB + +To inspect the data stored in MongoDB by SentryFlow, you can use the following command: + +``` +$ export MONGODB_POD=$(kubectl get pod -n sentryflow -l app=mongodb -o jsonpath='{.items[0].metadata.name}') +$ kubectl exec -it $MONGODB_POD -n sentryflow mongosh +``` + +Initiating this command will launch an interactive shell that can be used to explore the contents stored within the database. To examine the data in the database, refer to the subsequent commands provided. + +``` +test> use sentryflow; +switched to db sentryflow +sentryflow> db["api-logs"].find() +[ + { + _id: ObjectId('65ccfa872b80bf0cec7dab83'), + timestamp: '[2024-02-14T17:38:14.330Z]', + id: Long('1707929670787151'), + srcnamespace: 'default', + srcname: 'sleep-75bbc86479-fmf4p', + srclabel: { + app: 'sleep', + 'pod-template-hash': '75bbc86479', + 'security.istio.io/tlsMode': 'istio', + 'service.istio.io/canonical-name': 'sleep', + 'service.istio.io/canonical-revision': 'latest' + }, + srcip: '10.244.140.11', + srcport: '47996', + srctype: 'Pod', + dstnamespace: 'default', + dstname: 'httpbin', + dstlabel: { app: 'httpbin', service: 'httpbin' }, + dstip: '10.105.103.198', + dstport: '8000', + dsttype: 'Service', + protocol: 'HTTP/1.1', + method: 'GET', + path: '/status/418', + responsecode: Long('418') + } +] +``` diff --git a/examples/httpbin/telemetry.yaml b/examples/httpbin/telemetry.yaml new file mode 100644 index 0000000..78a6809 --- /dev/null +++ b/examples/httpbin/telemetry.yaml @@ -0,0 +1,11 @@ +apiVersion: telemetry.istio.io/v1alpha1 +kind: Telemetry +metadata: + name: sleep-logging +spec: + selector: + matchLabels: + app: sleep + accessLogging: + - providers: + - name: sentryflow diff --git a/examples/nephio/free5gc/README.md b/examples/nephio/free5gc/README.md new file mode 100644 index 0000000..d49f0ce --- /dev/null +++ b/examples/nephio/free5gc/README.md @@ -0,0 +1,299 @@ +# Nephio - Free5GC + +This example demonstrates capturing access logs from [Nephio](https://github.com/nephio-project/nephio), which operates on top of Istio using SentryFlow for log collection. + +> **Note**: The information about Nephio provided in this document may be outdated, as Nephio is currently in the early stages of development. + +## Step 1. Setting Up Nephio and Istio + +In this document, we will discuss monitoring `free5gc-cp` from the `regional` cluster to observe API activities within the control plane. + +> **Note**: To configure Nephio, please consult their official documentation available [here](https://github.com/nephio-project/docs/blob/main/content/en/docs/guides/user-guides/exercise-1-free5gc.md). Additionally, for the purpose of this document, it will be assumed that all steps up to and including **Step 6** have been executed correctly. + +Ensure that the Nephio `regional` cluster is functioning correctly, as well as the `free5gc-cp` namespaces within it. + +```bash +$ kubectl get pods --context regional-admin@regional -n free5gc-cp +NAME READY STATUS RESTARTS AGE +free5gc-ausf-69569f564b-7ttn5 1/1 Running 0 16s +free5gc-nrf-5978f8f797-xkhnl 1/1 Running 0 16s +free5gc-nssf-697b486564-gtpm5 1/1 Running 0 16s +free5gc-pcf-55d6c758bb-rhsm5 1/1 Running 0 16s +free5gc-udm-78464dcd7b-j6s7n 1/1 Running 0 16s +free5gc-udr-565445b596-7c6zw 1/1 Running 0 16s +free5gc-webui-ddd948585-nzkrf 1/1 Running 0 16s +mongodb-0 1/1 Running 0 7d9h +``` + +To gather access logs from within the namespace, Istio must be installed in the cluster. + +``` +$ istioctl install --set profile=default --context regional-admin@regional +This will install the Istio 1.20.2 "default" profile (with components: Istio core, Istiod, and Ingress gateways) into the cluster. Proceed? (y/N) y +✔ Istio core installed +✔ Istiod installed +✔ Ingress gateways installed +✔ Installation complete +Made this installation the default for injection and validation. +``` + +After successfully installing Istio in the cluster, you can verify that the Istio system is operational and running correctly by executing the following command: + +``` +$ kubectl get pods -n istio-system --context regional-admin@regional +``` + +## Step 2. Injecting Sidecars into Nephio + +Up to this point, Istio has been installed in the cluster where the `regional` cluster is operational. However, this does not necessarily mean that sidecar proxies are running alongside each pod. To ensure proper injection of sidecars into Nephio, the following steps need to be undertaken: + +### 2.1 Lowering Restriction: podSecurityStandard + +Nephio creates clusters for each type (e.g., `regional`, `edge01`, `edge02`) using **podSecurityContext**. By default, Nephio adheres to the following standards: + +- `enforce`: `baseline` +- `audit` and `warn`: `restricted` + +The security contexts employed by Nephio intentionally exclude the `NET_ADMIN` and `NET_RAW` capabilities, which are [required](https://istio.io/latest/docs/ops/deployment/requirements/) for the correct injection of the `istio-init` sidecar. Consequently, it is essential to explicitly designate these profiles as `privileged` across all namespaces to ensure Istio is injected properly. + +We can achieve this by: + +``` +$ kubectl label --overwrite ns --all pod-security.kubernetes.io/audit=privileged --context regional-admin@regional +$ kubectl label --overwrite ns --all pod-security.kubernetes.io/enforce=privileged --context regional-admin@regional +$ kubectl label --overwrite ns --all pod-security.kubernetes.io/warn=privileged --context regional-admin@regional +``` + +> **Note**: Modifying `podSecurityStandard` via `kubectl edit cluster regional-admin@regional` will reset the settings to their defaults. Therefore, it's recommended to directly alter the namespace configuration instead. + +Now, verify if those labels were set properly by: + +``` +$ kubectl describe ns free5gc-cp --context regional-admin@regional +Name: free5gc-cp +Labels: app.kubernetes.io/managed-by=configmanagement.gke.io + configsync.gke.io/declared-version=v1 + kubernetes.io/metadata.name=free5gc-cp + pod-security.kubernetes.io/audit=privileged + pod-security.kubernetes.io/enforce=privileged + pod-security.kubernetes.io/warn=privileged +... +``` + +### 2.2 Preparing Sidecars + +To inject sidecars using Istio, we will label the namespaces accordingly. For the purposes of this demonstration, we will specifically label the `free5gc-cp` namespaces. + +``` +$ kubectl label namespace free5gc-cp istio-injection=enabled --overwrite --context regional-admin@regional +namespace/free5gc-cp labeled +``` + +## Step 3. Deploying SentryFlow + +Now is the moment to deploy SentryFlow. This can be accomplished by executing the following steps: + +``` +$ kubectl create -f ../../../deployments/sentryflow.yaml --context regional-admin@regional +namespace/sentryflow created +serviceaccount/sa-sentryflow created +clusterrole.rbac.authorization.k8s.io/cr-sentryflow created +clusterrolebinding.rbac.authorization.k8s.io/rb-sentryflow created +deployment.apps/sentryflow created +service/sentryflow created +``` + +Also, we can deploy exporters for SentryFlow by following these additional steps: + +``` +$ kubectl create -f ../../../deployments/log-client.yaml --context regional-admin@regional +deployment.apps/log-client created + +$ kubectl create -f ../../../deployments/mongo-client.yaml --context regional-admin@regional +deployment.apps/mongodb created +service/mongodb created +deployment.apps/mongo-client created +``` + +Verify if Pods in SentryFlow are properly by: + +``` +$ kubectl get pods -n sentryflow --context regional-admin@regional +NAME READY STATUS RESTARTS AGE +log-client-75695cd4d4-z6rns 1/1 Running 0 37s +mongo-client-67dfb6ffbb-4psdh 1/1 Running 0 37s +mongodb-575549748d-9n6lx 1/1 Running 0 37s +sentryflow-5bf9f6987c-kmpgx 1/1 Running 0 60s +``` + +> **Note**: +The `sentryflow` namespace will not have `istio-injection=enabled`. Enabling this would result in each OpenTelemetry export being logged as an access log, leading to an excessive number of logs being captured. + +> **Note**: Deploying `sentryflow` will automatically modify the Istio mesh configuration (`istio-system/istio`) to direct the export of access logs to it. + +## Step 4. Restarting Deployments + +Till now we have: +- Setup SentryFlow +- Prepared Istio injection +- Lowered podSecurityStandard + +However, this action alone will not yet produce any logs. To enable Numbat to collect access logs, it is necessary to add `telemetry` configurations and also restart the deployments under `free5gc-cp`. + +> **Note**: Restarting deployments before implementing telemetry will result in the sidecars not transmitting access logs to our collector. Hence, it is important to apply telemetry configurations prior to restarting the deployments. + +Telemetry can be configured to monitor the `free5gc-cp` namespace by executing the following steps: + +``` +$ kubectl create -f telemetry.yaml --context regional-admin@regional +telemetry.telemetry.istio.io/free5gc-logging created +``` + +To restart all deployments within the `free5gc-cp` namespace, you can proceed with the following command: + +> **Note**: Restarting deployments within the `free5gc-cp` namespace is necessary. If there are any jobs currently running, additional steps may be needed to manage those jobs during the restart process. + +``` +$ kubectl rollout restart deployment -n free5gc-cp --context regional-admin@regional +deployment.apps/free5gc-ausf restarted +deployment.apps/free5gc-nrf restarted +deployment.apps/free5gc-nssf restarted +deployment.apps/free5gc-pcf restarted +deployment.apps/free5gc-udm restarted +deployment.apps/free5gc-udr restarted +deployment.apps/free5gc-webui restarted +``` + +After issuing the rollout restart command, you can verify whether the Pods now include sidecars by executing the following command: + +``` +$ kubectl get pods --context regional-admin@regional -n free5gc-cp +NAME READY STATUS RESTARTS AGE +free5gc-ausf-7d56c5f8db-bk54f 2/2 Running 0 21s +free5gc-nrf-7f7db5c645-kxfrc 2/2 Running 0 21s +free5gc-nssf-5477f65b9b-kfmbt 2/2 Running 0 21s +free5gc-pcf-c7b8ff6bb-t2zrq 2/2 Running 0 21s +free5gc-udm-65947bb776-xs6vf 2/2 Running 0 21s +free5gc-udr-67f5fdf44d-4ckwd 2/2 Running 0 21s +free5gc-webui-cf788755c-9bwzd 2/2 Running 0 21s +mongodb-0 1/1 Running 0 7d10h +``` + +Observing that each Pod now contains 2 containers instead of just 1 indicates the presence of sidecars. To confirm that the additional container is indeed the `istio-proxy`, you can use the `kubectl describe` command for further verification. + +## Step 5. Checking Logs + +Starting from this point, `sentryflow` will begin receiving logs from each deployment. To examine how deployments within the `free5gc-cp` namespace are communicating, there are two methods available: using a log client and a mongo client. + +### 5.1 Checking Logger + +The `log-client` deployment is configured to receive logs from `sentryflow` in our specified export format and output these logs as stdout. To view live logs, you can use the following command: + +``` +$ kubectl logs -n sentryflow -l app=log-client -f --context regional-admin@regional +``` + +This will show live logs such as: + +``` +2024/02/12 20:37:19 [Client] Received log: timeStamp:"[2024-02-12T20:37:19.318Z]" id:1707769691204491 srcNamespace:"free5gc-cp" srcName:"free5gc-pcf-c7b8ff6bb-t2zrq" srcLabel:{key:"nf" value:"pcf"} srcLabel:{key:"pod-template-hash" value:"c7b8ff6bb"} srcLabel:{key:"project" value:"free5gc"} srcLabel:{key:"security.istio.io/tlsMode" value:"istio"} srcLabel:{key:"service.istio.io/canonical-name" value:"free5gc-pcf"} srcLabel:{key:"service.istio.io/canonical-revision" value:"latest"} srcIP:"192.168.1.122" srcPort:"45542" srcType:"Pod" dstNamespace:"free5gc-cp" dstName:"nrf-nnrf" dstLabel:{key:"app.kubernetes.io/managed-by" value:"configmanagement.gke.io"} dstLabel:{key:"app.kubernetes.io/version" value:"v3.1.1"} dstLabel:{key:"configsync.gke.io/declared-version" value:"v1"} dstLabel:{key:"nf" value:"nrf"} dstLabel:{key:"project" value:"free5gc"} dstIP:"10.141.104.225" dstPort:"8000" dstType:"Service" protocol:"HTTP/2" method:"GET" path:"/nnrf-disc/v1/nf-instances?requester-nf-type=PCF&service-names=nudr-dr&target-nf-type=UDR" responseCode:200 +2024/02/12 20:37:20 [Client] Received log: timeStamp:"[2024-02-12T20:37:20.292Z]" id:1707769691204493 srcNamespace:"free5gc-cp" srcName:"free5gc-udm-65947bb776-xs6vf" srcLabel:{key:"nf" value:"udm"} srcLabel:{key:"pod-template-hash" value:"65947bb776"} srcLabel:{key:"project" value:"free5gc"} srcLabel:{key:"security.istio.io/tlsMode" value:"istio"} srcLabel:{key:"service.istio.io/canonical-name" value:"free5gc-udm"} srcLabel:{key:"service.istio.io/canonical-revision" value:"latest"} srcIP:"192.168.1.124" srcPort:"36488" srcType:"Pod" dstNamespace:"free5gc-cp" dstName:"nrf-nnrf" dstLabel:{key:"app.kubernetes.io/managed-by" value:"configmanagement.gke.io"} dstLabel:{key:"app.kubernetes.io/version" value:"v3.1.1"} dstLabel:{key:"configsync.gke.io/declared-version" value:"v1"} dstLabel:{key:"nf" value:"nrf"} dstLabel:{key:"project" value:"free5gc"} dstIP:"10.141.104.225" dstPort:"8000" dstType:"Service" protocol:"HTTP/2" method:"PUT" path:"/nnrf-nfm/v1/nf-instances/8ac564d2-e5cc-421c-96cc-8c57b9c85ded" responseCode:201 +2024/02/12 20:37:23 [Client] Received log: timeStamp:"[2024-02-12T20:37:23.594Z]" id:1707769691204495 srcNamespace:"free5gc-cp" srcName:"free5gc-ausf-7d56c5f8db-bk54f" srcLabel:{key:"nf" value:"ausf"} srcLabel:{key:"pod-template-hash" value:"7d56c5f8db"} srcLabel:{key:"project" value:"free5gc"} srcLabel:{key:"security.istio.io/tlsMode" value:"istio"} srcLabel:{key:"service.istio.io/canonical-name" value:"free5gc-ausf"} srcLabel:{key:"service.istio.io/canonical-revision" value:"latest"} srcIP:"192.168.1.126" srcPort:"35258" srcType:"Pod" dstNamespace:"free5gc-cp" dstName:"nrf-nnrf" dstLabel:{key:"app.kubernetes.io/managed-by" value:"configmanagement.gke.io"} dstLabel:{key:"app.kubernetes.io/version" value:"v3.1.1"} dstLabel:{key:"configsync.gke.io/declared-version" value:"v1"} dstLabel:{key:"nf" value:"nrf"} dstLabel:{key:"project" value:"free5gc"} dstIP:"10.141.104.225" dstPort:"8000" dstType:"Service" protocol:"HTTP/2" method:"PUT" path:"/nnrf-nfm/v1/nf-instances/9e1ddaeb-898f-4504-a247-b4a78b329a74" responseCode:201 +``` + +### 5.2 Checking MongoDB + +We have another client (`mongo-client`) that stores all data received from the `sentryflow` into the MongoDB deployment. You can use `mongosh` to inspect the contents stored in MongoDB by executing the following command: + +``` +$ export MONGODB_POD=$(kubectl get pod -n sentryflow -l app=mongodb --context regional-admin@regional -o jsonpath='{.items[0].metadata.name}') +$ kubectl exec -it $MONGODB_POD -n sentryflow --context regional-admin@regional mongosh +``` + +Once we have entered `mongosh` we can check entries stored in the DB. SentryFlow uses DB named `sentryflow` and collection `access-logs` for storing access logs. + +An example command of checking all access logs stored in DB would be: + +``` +test> use sentryflow +use sentryflow +sentryflow> db["api-logs"].find() +... + { + _id: ObjectId('65ca77e4ef0f86784e2fa544'), + timestamp: '[2024-02-12T19:56:19.298Z]', + id: Long('1707767512691239'), + srcnamespace: 'free5gc-cp', + srcname: 'free5gc-nssf-566df8589f-4wwt9', + srclabel: { + 'pod-template-hash': '566df8589f', + project: 'free5gc', + 'security.istio.io/tlsMode': 'istio', + 'service.istio.io/canonical-name': 'free5gc-nssf', + 'service.istio.io/canonical-revision': 'latest', + nf: 'nssf' + }, + srcip: '192.168.1.105', + srcport: '53008', + srctype: 'Pod', + dstnamespace: 'free5gc-cp', + dstname: 'nrf-nnrf', + dstlabel: { + 'app.kubernetes.io/managed-by': 'configmanagement.gke.io', + 'app.kubernetes.io/version': 'v3.1.1', + 'configsync.gke.io/declared-version': 'v1', + nf: 'nrf', + project: 'free5gc' + }, + dstip: '10.141.104.225', + dstport: '8000', + dsttype: 'Service', + protocol: 'HTTP/2', + method: 'PUT', + path: '/nnrf-nfm/v1/nf-instances/99608079-71a4-48cd-9e0c-be0837655d2f', + responsecode: Long('201') + }, +... +``` + +Another example would involve filtering out only logs with `protocol":"HTTP/1.1` to specifically examine API calls: + +``` +sentryflow> db["access-logs"].find({"protocol":"HTTP/1.1"}) +... + { + _id: ObjectId('65ca77e4ef0f86784e2fa545'), + timestamp: '[2024-02-12T19:56:19.350Z]', + id: Long('1707767512691241'), + srcnamespace: 'free5gc-cp', + srcname: 'free5gc-nssf-566df8589f-4wwt9', + srclabel: { + 'security.istio.io/tlsMode': 'istio', + 'service.istio.io/canonical-name': 'free5gc-nssf', + 'service.istio.io/canonical-revision': 'latest', + nf: 'nssf', + 'pod-template-hash': '566df8589f', + project: 'free5gc' + }, + srcip: '192.168.1.105', + srcport: '45888', + srctype: 'Pod', + dstnamespace: 'free5gc-cp', + dstname: 'free5gc-nrf-6f6484c6cb-cpnzk', + dstlabel: { + nf: 'nrf', + 'pod-template-hash': '6f6484c6cb', + project: 'free5gc', + 'security.istio.io/tlsMode': 'istio', + 'service.istio.io/canonical-name': 'free5gc-nrf', + 'service.istio.io/canonical-revision': 'latest' + }, + dstip: '192.168.1.94', + dstport: '8000', + dsttype: 'Pod', + protocol: 'HTTP/1.1', + method: 'PUT', + path: '/nnrf-nfm/v1/nf-instances/99608079-71a4-48cd-9e0c-be0837655d2f', + responsecode: Long('201') +... +``` diff --git a/examples/nephio/free5gc/telemetry.yaml b/examples/nephio/free5gc/telemetry.yaml new file mode 100644 index 0000000..4e40892 --- /dev/null +++ b/examples/nephio/free5gc/telemetry.yaml @@ -0,0 +1,9 @@ +apiVersion: telemetry.istio.io/v1alpha1 +kind: Telemetry +metadata: + name: free5gc-logging + namespace: istio-system +spec: + accessLogging: + - providers: + - name: sentryflow diff --git a/examples/nephio/oai/README.md b/examples/nephio/oai/README.md new file mode 100644 index 0000000..18bdc38 --- /dev/null +++ b/examples/nephio/oai/README.md @@ -0,0 +1,292 @@ +# Nephio - OAI + + +This example demonstrates how to capture access logs from the [Nephio](https://github.com/nephio-project/nephio)'s OAI Demo, which operates on top of Istio, utilizing SentryFlow for log capture. + +> **Note**: The information about Nephio provided in this document may be outdated, as Nephio is currently in the early stages of development. + +## Step 1. Setting Up Nephio and Istio + +In this document, we will discuss how to monitor the `oai-core` component within the `core` cluster to observe API activities in the control plane. + +> **Note**: To set up Nephio, please consult the official OAI documentation available [here](https://github.com/nephio-project/docs/blob/main/content/en/docs/guides/user-guides/exercise-2-oai.md). For the purposes of this document, it will be assumed that all steps up to and including **Step 5** have been executed correctly. + +Ensure that the Nephio `core` cluster is functioning correctly, as well as the `oai-core` namespaces within it. + +```bash +$ kubectl get pods -n oai-core --context core-admin@core +NAME READY STATUS RESTARTS AGE +amf-core-56c68b7487-g2clh 1/1 Running 0 10h +ausf-core-7885cb865-hd9pz 1/1 Running 0 10h +mysql-7dd4cc6945-pj6xz 1/1 Running 0 10h +nrf-core-d4f69557d-wptds 1/1 Running 0 10h +smf-core-59bcf4576c-t6rwr 1/1 Running 0 10h +udm-core-c7d67cb4d-r4zwn 1/1 Running 0 10h +udr-core-69c56bcbd5-whjb9 1/1 Running 0 10h +``` + +To gather access logs from within the namespace, Istio must be installed in the cluster. + +``` +$ istioctl install --set profile=default --context core-admin@core +This will install the Istio 1.20.2 "default" profile (with components: Istio core, Istiod, and Ingress gateways) into the cluster. Proceed? (y/N) y +✔ Istio core installed +✔ Istiod installed +✔ Ingress gateways installed +✔ Installation complete +Made this installation the default for injection and validation. +``` + +After successfully installing Istio in the cluster, you can verify that the Istio system is operational and running correctly by executing the following command: + +``` +$ kubectl get pods -n istio-system --context core-admin@core +``` + +## Step 2. Injecting Sidecars into Nephio + +Up to this point, Istio has been installed in the cluster where the `edge` cluster is operational. However, this does not necessarily mean that sidecar proxies are running alongside each pod. To ensure proper injection of sidecars into Nephio, the following steps need to be undertaken: + +### 2.1 Lowering Restriction: podSecurityStandard + +Nephio creates clusters for each type (e.g., `core`, `edge`, `regional`) using **podSecurityContext**. By default, Nephio adheres to the following standards: + +- `enforce`: `baseline` +- `audit` and `warn`: `restricted` + +The security contexts employed by Nephio intentionally exclude the `NET_ADMIN` and `NET_RAW` capabilities, which are [required](https://istio.io/latest/docs/ops/deployment/requirements/) for the correct injection of the `istio-init` sidecar. Consequently, it is essential to explicitly designate these profiles as `privileged` across all namespaces to ensure Istio is injected properly. + +We can achieve this by: + +``` +$ kubectl label --overwrite ns --all pod-security.kubernetes.io/audit=privileged --context core-admin@core +$ kubectl label --overwrite ns --all pod-security.kubernetes.io/enforce=privileged --context core-admin@core +$ kubectl label --overwrite ns --all pod-security.kubernetes.io/warn=privileged --context core-admin@core +``` + +> **Note**: Modifying `podSecurityStandard` via `kubectl edit cluster regional-admin@regional` will reset the settings to their defaults. Therefore, it's recommended to directly alter the namespace configuration instead. + +Now, verify if those labels were set properly by: + +``` +$ kubectl describe ns oai-core --context core-admin@core +Name: oai-core +Labels: app.kubernetes.io/managed-by=configmanagement.gke.io + configsync.gke.io/declared-version=v1 + kubernetes.io/metadata.name=oai-core + pod-security.kubernetes.io/audit=privileged + pod-security.kubernetes.io/enforce=privileged + pod-security.kubernetes.io/warn=privileged +... +``` + +### 2.2 Preparing Sidecars + +To inject sidecars using Istio, we will label the namespaces accordingly. For the purposes of this demonstration, we will specifically label the `core-admin@core` namespaces. + +``` +$ kubectl label namespace oai-core istio-injection=enabled --overwrite --context core-admin@core +namespace/oai-core labeled +``` + +## Step 3. Deploying SentryFlow + +Now is the moment to deploy SentryFlow. This can be accomplished by executing the following steps: + +``` +$ kubectl create -f ../../../deployments/sentryflow.yaml --context core-admin@core +namespace/sentryflow created +serviceaccount/sa-sentryflow created +clusterrole.rbac.authorization.k8s.io/cr-sentryflow created +clusterrolebinding.rbac.authorization.k8s.io/rb-sentryflow created +deployment.apps/sentryflow created +service/sentryflow created +``` + +Also, we can deploy exporters for SentryFlow by following these additional steps: + +``` +$ kubectl create -f ../../../deployments/log-client.yaml --context core-admin@core +deployment.apps/log-client created + +$ kubectl create -f ../../../deployments/mongo-client.yaml --context regional-admin@regional +deployment.apps/mongodb created +service/mongodb created +deployment.apps/mongo-client created +``` + +Verify if Pods in SentryFlow are properly by: + +``` +$ kubectl get pods -n sentryflow --context regional-admin@regional +NAME READY STATUS RESTARTS AGE +log-client-75695cd4d4-z6rns 1/1 Running 0 37s +mongo-client-67dfb6ffbb-4psdh 1/1 Running 0 37s +mongodb-575549748d-9n6lx 1/1 Running 0 37s +sentryflow-5bf9f6987c-kmpgx 1/1 Running 0 60s +``` + +> **Note**: +The `sentryflow` namespace will not have `istio-injection=enabled`. Enabling this would result in each OpenTelemetry export being logged as an access log, leading to an excessive number of logs being captured. + +> **Note**: Deploying `sentryflow` will automatically modify the Istio mesh configuration (`istio-system/istio`) to direct the export of access logs to it. + +## Step 4. Restarting Deployments + +Till now we have: +- Setup SentryFlow +- Prepared Istio injection +- Lowered podSecurityStandard + +However, this action alone will not yet produce any logs. To enable Numbat to collect access logs, it is necessary to add `telemetry` configurations and also restart the deployments under `oai-logging`. + +> **Note**: Restarting deployments before implementing telemetry will result in the sidecars not transmitting access logs to our collector. Hence, it is important to apply telemetry configurations prior to restarting the deployments. + +Telemetry can be configured to monitor the `oai-logging` namespace by executing the following steps: + +``` +$ kubectl create -f ./telemetry.yaml --context core-admin@core +telemetry.telemetry.istio.io/oai-logging created +``` + +To restart all deployments within the `oai-logging` namespace, you can proceed with the following command: + +> **Note**: Restarting deployments within the `oai-logging` namespace is necessary. If there are any jobs currently running, additional steps may be needed to manage those jobs during the restart process. + +``` +$ kubectl rollout restart deployments -n oai-core --context core-admin@core +deployment.apps/amf-core restarted +deployment.apps/ausf-core restarted deployment.apps/mysql restarted +deployment.apps/nrf-core restarted +deployment.apps/smf-core restarted +deployment.apps/udm-core restarted +deployment.apps/udr-core restarted +``` + +After issuing the rollout restart command, you can verify whether the Pods now include sidecars by executing the following command: + +``` +$ kubectl get pods -n oai-core --context core-admin@core +NAME READY STATUS RESTARTS AGE +amf-core-76967858c4-w4mlt 2/2 Running 0 8m3s +ausf-core-6bfd5576c5-sprb4 2/2 Running 0 8m10s +mysql-764b8f5ff5-7hgcv 2/2 Running 0 8m2s +nrf-core-5c74f7cdb4-mrk4w 2/2 Running 0 8m10s +smf-core-57bbdf59c4-x4jnk 2/2 Running 0 8m5s +udm-core-85c5478b94-bm4mv 2/2 Running 0 8m10s +... +``` + +Observing that each Pod now contains 2 containers instead of just 1 indicates the presence of sidecars. To confirm that the additional container is indeed the `istio-proxy`, you can use the `kubectl describe` command for further verification. + +## Step 5. Checking Logs + +Starting from this point, `sentryflow` will begin receiving logs from each deployment. To examine how deployments within the `oai-core` namespace are communicating, there are two methods available: using a log client and a mongo client. + +### 5.1 Checking Logs using a Log Client + +The `log-client` deployment is configured to receive logs from `sentryflow` in our specified export format and output these logs as stdout. To view live logs, you can use the following command: + +``` +$ kubectl logs -n sentryflow -l app=log-client -f --context core-admin@core +``` + +This will show live logs such as: + +``` +2024/02/15 03:45:30 [Client] Received log: timeStamp:"[2024-02-15T03:45:30.153Z]" id:1707968675718909 srcNamespace:"oai-core" srcName:"smf-core-57bbdf59c4-x4jnk" srcLabel:{key:"app.kubernetes.io/managed-by" value:"configmanagement.gke.io"} srcLabel:{key:"configsync.gke.io/declared-version" value:"v1alpha1"} srcLabel:{key:"pod-template-hash" value:"57bbdf59c4"} srcLabel:{key:"security.istio.io/tlsMode" value:"istio"} srcLabel:{key:"service.istio.io/canonical-name" value:"smf-core"} srcLabel:{key:"service.istio.io/canonical-revision" value:"latest"} srcLabel:{key:"workload.nephio.org/oai" value:"smf"} srcIP:"192.168.1.57" srcPort:"42954" srcType:"Pod" dstNamespace:"oai-core" dstName:"nrf-core-5c74f7cdb4-mrk4w" dstLabel:{key:"app.kubernetes.io/managed-by" value:"configmanagement.gke.io"} dstLabel:{key:"configsync.gke.io/declared-version" value:"v1alpha1"} dstLabel:{key:"pod-template-hash" value:"5c74f7cdb4"} dstLabel:{key:"security.istio.io/tlsMode" value:"istio"} dstLabel:{key:"service.istio.io/canonical-name" value:"nrf-core"} dstLabel:{key:"service.istio.io/canonical-revision" value:"latest"} dstLabel:{key:"workload.nephio.org/oai" value:"nrf"} dstIP:"192.168.1.55" dstPort:"80" dstType:"Pod" protocol:"HTTP/2" method:"GET" path:"/nnrf-nfm/v1/nf-instances?nf-type=NRF" responseCode:503 +2024/02/15 03:45:30 [Client] Received log: timeStamp:"[2024-02-15T03:45:30.732Z]" id:1707968675718911 srcNamespace:"oai-core" srcName:"udm-core-85c5478b94-bm4mv" srcLabel:{key:"app.kubernetes.io/managed-by" value:"configmanagement.gke.io"} srcLabel:{key:"configsync.gke.io/declared-version" value:"v1alpha1"} srcLabel:{key:"pod-template-hash" value:"85c5478b94"} srcLabel:{key:"security.istio.io/tlsMode" value:"istio"} srcLabel:{key:"service.istio.io/canonical-name" value:"udm-core"} srcLabel:{key:"service.istio.io/canonical-revision" value:"latest"} srcLabel:{key:"workload.nephio.org/oai" value:"udm"} srcIP:"192.168.1.54" srcPort:"48406" srcType:"Pod" dstNamespace:"oai-core" dstName:"nrf-core-5c74f7cdb4-mrk4w" dstLabel:{key:"app.kubernetes.io/managed-by" value:"configmanagement.gke.io"} dstLabel:{key:"configsync.gke.io/declared-version" value:"v1alpha1"} dstLabel:{key:"pod-template-hash" value:"5c74f7cdb4"} dstLabel:{key:"security.istio.io/tlsMode" value:"istio"} dstLabel:{key:"service.istio.io/canonical-name" value:"nrf-core"} dstLabel:{key:"service.istio.io/canonical-revision" value:"latest"} dstLabel:{key:"workload.nephio.org/oai" value:"nrf"} dstIP:"192.168.1.55" dstPort:"80" dstType:"Pod" protocol:"HTTP/2" method:"GET" path:"/nnrf-nfm/v1/nf-instances?nf-type=NRF" responseCode:503 +``` + +### 5.2 Checking Logs in MongoDB + + +We have another client (`mongo-client`) that stores all data received from the `sentryflow` into the MongoDB deployment. You can use `mongosh` to inspect the contents stored in MongoDB by executing the following command: + +``` +$ export MONGODB_POD=$(kubectl get pod -n sentryflow -l app=mongodb --context core-admin@core -o jsonpath='{.items[0].metadata.name}') +$ kubectl exec -it $MONGODB_POD -n sentryflow --context core-admin@core mongosh +``` + +Once we have entered `mongosh` we can check entries stored in the DB. SentryFlow uses DB named `sentryflow` and collection `api-logs` for storing access logs. + +An example command for retrieving all access logs stored in the database would be: + +``` +test> use sentryflow +use sentryflow +sentryflow> db["api-logs"].find() +... + { + _id: ObjectId('65ca77e4ef0f86784e2fa544'), + timestamp: '[2024-02-12T19:56:19.298Z]', + id: Long('1707767512691239'), + srcnamespace: 'free5gc-cp', + srcname: 'free5gc-nssf-566df8589f-4wwt9', + srclabel: { + 'pod-template-hash': '566df8589f', + project: 'free5gc', + 'security.istio.io/tlsMode': 'istio', + 'service.istio.io/canonical-name': 'free5gc-nssf', + 'service.istio.io/canonical-revision': 'latest', + nf: 'nssf' + }, + srcip: '192.168.1.105', + srcport: '53008', + srctype: 'Pod', + dstnamespace: 'free5gc-cp', + dstname: 'nrf-nnrf', + dstlabel: { + 'app.kubernetes.io/managed-by': 'configmanagement.gke.io', + 'app.kubernetes.io/version': 'v3.1.1', + 'configsync.gke.io/declared-version': 'v1', + nf: 'nrf', + project: 'free5gc' + }, + dstip: '10.141.104.225', + dstport: '8000', + dsttype: 'Service', + protocol: 'HTTP/2', + method: 'PUT', + path: '/nnrf-nfm/v1/nf-instances/99608079-71a4-48cd-9e0c-be0837655d2f', + responsecode: Long('201') + }, +... +``` + +Another example would involve filtering out only logs with `protocol":"HTTP/2` to specifically examine API calls: + +``` +sentryflow> db["api-logs"].find({"protocol":"HTTP/2"}) +... + { + _id: ObjectId('65cd871bb9e996068ab49250'), + timestamp: '[2024-02-15T03:38:02.636Z]', + id: Long('1707968025200999'), + srcnamespace: 'kube-system', + srcname: 'kube-scheduler-core-cxfgb-gt8tr', + srclabel: { component: 'kube-scheduler', tier: 'control-plane' }, + srcip: '172.18.0.5', + srcport: '3479', + srctype: 'Pod', + dstnamespace: 'oai-core', + dstname: 'nrf-core-696b59c448-4dn52', + dstlabel: { + 'pod-template-hash': '696b59c448', + 'security.istio.io/tlsMode': 'istio', + 'service.istio.io/canonical-name': 'nrf-core', + 'service.istio.io/canonical-revision': 'latest', + 'workload.nephio.org/oai': 'nrf', + 'app.kubernetes.io/managed-by': 'configmanagement.gke.io', + 'configsync.gke.io/declared-version': 'v1alpha1' + }, + dstip: '192.168.1.35', + dstport: '80', + dsttype: 'Pod', + protocol: 'HTTP/2', + method: 'PATCH', + path: '/nnrf-nfm/v1/nf-instances/863bdd79-b36b-4c85-b6ed-61bfc1cb5de3', + responsecode: Long('503') + }, +... +``` diff --git a/examples/nephio/oai/telemetry.yaml b/examples/nephio/oai/telemetry.yaml new file mode 100644 index 0000000..d7df065 --- /dev/null +++ b/examples/nephio/oai/telemetry.yaml @@ -0,0 +1,9 @@ +apiVersion: telemetry.istio.io/v1alpha1 +kind: Telemetry +metadata: + name: oai-logging + namespace: istio-system +spec: + accessLogging: + - providers: + - name: sentryflow diff --git a/examples/robotshop/README.md b/examples/robotshop/README.md new file mode 100644 index 0000000..635a596 --- /dev/null +++ b/examples/robotshop/README.md @@ -0,0 +1,15 @@ +# Example - robotshop + +## Installation + +https://github.com/instana/robot-shop + +Simple Microservice application. this is installed using helm. + +## Namespace Telemetry + +```kubectl create -f telemetry.yaml``` + +## Accessing the Store + +Connect to http://localhost:8080 and make API requests. diff --git a/examples/robotshop/telemetry.yaml b/examples/robotshop/telemetry.yaml new file mode 100644 index 0000000..9504c0c --- /dev/null +++ b/examples/robotshop/telemetry.yaml @@ -0,0 +1,9 @@ +apiVersion: telemetry.istio.io/v1alpha1 +kind: Telemetry +metadata: + name: robot-shop-logging + namespace: robot-shop +spec: + accessLogging: + - providers: + - name: sentryflow diff --git a/protobuf/.gitignore b/protobuf/.gitignore new file mode 100644 index 0000000..cd1cbd6 --- /dev/null +++ b/protobuf/.gitignore @@ -0,0 +1,2 @@ +.idea/ +*.pb.go \ No newline at end of file diff --git a/protobuf/Makefile b/protobuf/Makefile new file mode 100644 index 0000000..eea513e --- /dev/null +++ b/protobuf/Makefile @@ -0,0 +1,16 @@ +PROTO:=sentryflow.proto +PBGO:=$(PROTO:.proto=.pb.go) + +.PHONY: build +build: $(PBGO) go.sum + +go.sum: go.mod + go get . + +%.pb.go: %.proto + go mod tidy + protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative,require_unimplemented_servers=false $< + +.PHONY: clean +clean: + rm -f go.sum *.pb.go \ No newline at end of file diff --git a/protobuf/go.mod b/protobuf/go.mod new file mode 100644 index 0000000..6d856bd --- /dev/null +++ b/protobuf/go.mod @@ -0,0 +1,16 @@ +module github.com/5GSEC/sentryflow/protobuf + +go 1.19 + +require ( + google.golang.org/grpc v1.61.1 + google.golang.org/protobuf v1.32.0 +) + +require ( + github.com/golang/protobuf v1.5.3 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect +) diff --git a/protobuf/go.sum b/protobuf/go.sum new file mode 100644 index 0000000..3ca0166 --- /dev/null +++ b/protobuf/go.sum @@ -0,0 +1,20 @@ +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/protobuf/sentryflow.proto b/protobuf/sentryflow.proto new file mode 100644 index 0000000..9421b5a --- /dev/null +++ b/protobuf/sentryflow.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package protobuf; + +option go_package = "sentryflow/protobuf"; + +message ClientInfo { + string hostName = 1; + string IPAddress = 2; +} + +message APILog { + uint64 id = 1; + string timeStamp = 2; + + string srcNamespace = 11; + string srcName = 12; + map srcLabel = 13; + string srcType = 16; + string srcIP = 17; + string srcPort = 18; + + string dstNamespace = 21; + string dstName = 22; + map dstLabel = 23; + string dstType = 26; + string dstIP = 27; + string dstPort = 28; + + string protocol = 31; + string method = 32; + string path = 33; + int32 responseCode = 34; +} + +message APIMetric { + map perAPICounts = 1; + // @todo: add some more metrics here +} + +service SentryFlow { + rpc GetLog(ClientInfo) returns (stream APILog); + rpc GetAPIMetrics(ClientInfo) returns (APIMetric); +} diff --git a/sentryflow-clients/Makefile b/sentryflow-clients/Makefile new file mode 100644 index 0000000..51933e4 --- /dev/null +++ b/sentryflow-clients/Makefile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: Apache-2.0 + +.PHONY: gofmt +gofmt: + cd $(CURDIR); gofmt -w -s -d $(shell find . -type f -name '*.go' -print) + +.PHONY: golint +golint: +ifeq (, $(shell which golint)) + @{ \ + set -e ;\ + GOLINT_TEMP_DIR=$$(mktemp -d) ;\ + cd $$GOLINT_TEMP_DIR ;\ + go mod init tmp ;\ + go get golang.org/x/lint/golint ;\ + go install golang.org/x/lint/golint ;\ + rm -rf $$GOLINT_TEMP_DIR ;\ + } +endif + cd $(CURDIR); golint ./... + +.PHONY: gosec +gosec: +ifeq (, $(shell which gosec)) + @{ \ + set -e ;\ + GOSEC_TEMP_DIR=$$(mktemp -d) ;\ + cd $$GOSEC_TEMP_DIR ;\ + go mod init tmp ;\ + go get github.com/securego/gosec/v2/cmd/gosec ;\ + go install github.com/securego/gosec/v2/cmd/gosec ;\ + rm -rf $$GOSEC_TEMP_DIR ;\ + } +endif + cd $(CURDIR); gosec -exclude=G402 ./... \ No newline at end of file diff --git a/sentryflow-clients/README.md b/sentryflow-clients/README.md new file mode 100644 index 0000000..999453c --- /dev/null +++ b/sentryflow-clients/README.md @@ -0,0 +1,2 @@ +# SentryFlow Clients + diff --git a/sentryflow-clients/log-client/Dockerfile b/sentryflow-clients/log-client/Dockerfile new file mode 100644 index 0000000..e0f7f5b --- /dev/null +++ b/sentryflow-clients/log-client/Dockerfile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: Apache-2.0 + +### Builder + +FROM golang:1.19-alpine3.17 as builder + +RUN apk --no-cache update +RUN apk add --no-cache git clang llvm make gcc protobuf + +RUN mkdir /app +RUN mkdir /protobuf + +WORKDIR /protobuf + +COPY /protobuf . + +WORKDIR /app + +COPY /sentryflow-clients/log-client . + +RUN go install github.com/golang/protobuf/protoc-gen-go@latest +RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +RUN go build -o log-client + +### Make executable image + +FROM alpine:3.18 as client + +RUN echo "@community http://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories + +RUN apk --no-cache update +RUN apk add bash + +COPY --from=builder /app/log-client / + +CMD ["/log-client"] diff --git a/sentryflow-clients/log-client/Makefile b/sentryflow-clients/log-client/Makefile new file mode 100644 index 0000000..7b8bfdb --- /dev/null +++ b/sentryflow-clients/log-client/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +IMAGE_NAME = 5gsec/sentryflow-log-client +TAG = v0.1 + +.PHONY: build + +build: + docker build -t $(IMAGE_NAME):$(TAG) -f ./Dockerfile ../../ + +.PHONY: clean + +clean: + docker rmi $(IMAGE_NAME):$(TAG) + +.PHONY: run + +run: + docker run -it --rm $(IMAGE_NAME):$(TAG) diff --git a/sentryflow-clients/log-client/common/config.go b/sentryflow-clients/log-client/common/config.go new file mode 100644 index 0000000..ad58d25 --- /dev/null +++ b/sentryflow-clients/log-client/common/config.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 + +package common + +import ( + "errors" + "fmt" + "os" + "strconv" +) + +type Config struct { + ServerAddr string + ServerPort int +} + +// Cfg is for global reference +var Cfg Config + +// LoadEnvVars loads environment variables and stores them as global variable +func LoadEnvVars() (Config, error) { + var err error + + // load listen address and check if valid + Cfg.ServerAddr = os.Getenv("SERVER_ADDR") + + // load listen port and check if valid + Cfg.ServerPort, err = strconv.Atoi(os.Getenv("SERVER_PORT")) + if err != nil { + msg := fmt.Sprintf("invalid server port %s: %v", os.Getenv("SERVER_PORT"), err) + return Cfg, errors.New(msg) + } + + return Cfg, nil +} diff --git a/sentryflow-clients/log-client/go.mod b/sentryflow-clients/log-client/go.mod new file mode 100644 index 0000000..3b9c87b --- /dev/null +++ b/sentryflow-clients/log-client/go.mod @@ -0,0 +1,19 @@ +module log-client + +go 1.19 + +replace sentryflow/protobuf => ../../protobuf + +require ( + google.golang.org/grpc v1.61.1 + sentryflow/protobuf v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/golang/protobuf v1.5.3 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) diff --git a/sentryflow-clients/log-client/go.sum b/sentryflow-clients/log-client/go.sum new file mode 100644 index 0000000..75a2c7a --- /dev/null +++ b/sentryflow-clients/log-client/go.sum @@ -0,0 +1,20 @@ +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/sentryflow-clients/log-client/main.go b/sentryflow-clients/log-client/main.go new file mode 100644 index 0000000..27767e9 --- /dev/null +++ b/sentryflow-clients/log-client/main.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "fmt" + "google.golang.org/grpc" + _ "google.golang.org/grpc/encoding/gzip" // If not set, encoding problem occurs https://stackoverflow.com/questions/74062727 + "io" + "log" + "log-client/common" + "os" + "sentryflow/protobuf" +) + +func main() { + // Load environment variables + cfg, err := common.LoadEnvVars() + if err != nil { + log.Fatalf("Could not load environment variables: %v", err) + } + + // Construct address and start listening + addr := fmt.Sprintf("%s:%d", cfg.ServerAddr, cfg.ServerPort) + + // Set up a connection to the server. + conn, err := grpc.Dial(addr, grpc.WithInsecure()) + if err != nil { + log.Fatalf("could not connect: %v", err) + } + defer conn.Close() + + // Start serving gRPC server + log.Printf("[gRPC] Successfully connected to %s", addr) + + // Create a client for the SentryFlow service + client := protobuf.NewSentryFlowClient(conn) + + hostname, err := os.Hostname() + if err != nil { + log.Fatalf("could not find hostname: %v", err) + } + + // Define the client information + clientInfo := &protobuf.ClientInfo{ + HostName: hostname, + } + + // Contact the server and print out its response + stream, err := client.GetLog(context.Background(), clientInfo) + if err != nil { + log.Fatalf("could not get log: %v", err) + } + + for { + data, err := stream.Recv() + if err == io.EOF { + break + } + if err != nil { + log.Fatalf("failed to receive log: %v", err) + } + log.Printf("[Client] Received log: %v", data) + } +} diff --git a/sentryflow-clients/mongo-client/Dockerfile b/sentryflow-clients/mongo-client/Dockerfile new file mode 100644 index 0000000..1a0d3ca --- /dev/null +++ b/sentryflow-clients/mongo-client/Dockerfile @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: Apache-2.0 + +### Builder + +FROM golang:1.19-alpine3.17 as builder + +RUN apk --no-cache update +RUN apk add --no-cache git clang llvm make gcc protobuf + +RUN mkdir /app +RUN mkdir /protobuf + +WORKDIR /protobuf + +COPY /protobuf . + +WORKDIR /app + +COPY /sentryflow-clients/mongo-client . + +RUN go install github.com/golang/protobuf/protoc-gen-go@latest +RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +RUN go build -o mongo-client + +### Make executable image + +FROM alpine:3.18 as client + +RUN echo "@community http://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories + +RUN apk --no-cache update +RUN apk add bash + +COPY --from=builder /app/mongo-client / + +CMD ["/mongo-client"] diff --git a/sentryflow-clients/mongo-client/Makefile b/sentryflow-clients/mongo-client/Makefile new file mode 100644 index 0000000..c1cba29 --- /dev/null +++ b/sentryflow-clients/mongo-client/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +IMAGE_NAME = 5gsec/sentryflow-mongo-client +TAG = v0.1 + +.PHONY: build + +build: + docker build -t $(IMAGE_NAME):$(TAG) -f ./Dockerfile ../../ + +.PHONY: clean + +clean: + docker rmi $(IMAGE_NAME):$(TAG) + +.PHONY: run + +run: + docker run -it --rm $(IMAGE_NAME):$(TAG) diff --git a/sentryflow-clients/mongo-client/common/config.go b/sentryflow-clients/mongo-client/common/config.go new file mode 100644 index 0000000..ad58d25 --- /dev/null +++ b/sentryflow-clients/mongo-client/common/config.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 + +package common + +import ( + "errors" + "fmt" + "os" + "strconv" +) + +type Config struct { + ServerAddr string + ServerPort int +} + +// Cfg is for global reference +var Cfg Config + +// LoadEnvVars loads environment variables and stores them as global variable +func LoadEnvVars() (Config, error) { + var err error + + // load listen address and check if valid + Cfg.ServerAddr = os.Getenv("SERVER_ADDR") + + // load listen port and check if valid + Cfg.ServerPort, err = strconv.Atoi(os.Getenv("SERVER_PORT")) + if err != nil { + msg := fmt.Sprintf("invalid server port %s: %v", os.Getenv("SERVER_PORT"), err) + return Cfg, errors.New(msg) + } + + return Cfg, nil +} diff --git a/sentryflow-clients/mongo-client/db/dbHandler.go b/sentryflow-clients/mongo-client/db/dbHandler.go new file mode 100644 index 0000000..ae94049 --- /dev/null +++ b/sentryflow-clients/mongo-client/db/dbHandler.go @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 + +package db + +import ( + "context" + "errors" + "fmt" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "log" + "os" + protobuf "sentryflow/protobuf" + "time" +) + +type Handler struct { + client *mongo.Client + database *mongo.Database + collection *mongo.Collection + cancel context.CancelFunc + dbURL string +} + +var Manager *Handler + +// New creates a new mongoDB handler +func New() (*Handler, error) { + dbHost := os.Getenv("MONGODB_HOST") + h := Handler{} + var err error + + // Environment variable was not set + if dbHost == "" { + return nil, errors.New("$MONGODB_HOST not set") + } + + // Create a MongoDB client + h.client, err = mongo.NewClient(options.Client().ApplyURI(dbHost)) + if err != nil { + msg := fmt.Sprintf("unable to initialize monogoDB client for %s: %v", dbHost, err) + return nil, errors.New(msg) + } + + // Set timeout (10 sec) + var ctx context.Context + ctx, h.cancel = context.WithTimeout(context.Background(), 10*time.Second) + + // Try connecting the server + err = h.client.Connect(ctx) + if err != nil { + msg := fmt.Sprintf("unable to connect mongoDB server %s: %v", dbHost, err) + return nil, errors.New(msg) + } + + // Create 'sentryflow' database and 'api-logs' collection + h.database = h.client.Database("sentryflow") + h.collection = h.database.Collection("api-logs") + + Manager = &h + return &h, nil +} + +func (h *Handler) Disconnect() { + err := h.client.Disconnect(context.Background()) + if err != nil { + log.Printf("unable to properly disconnect: %v", err) + } + + return +} + +func (h *Handler) InsertData(data *protobuf.APILog) error { + _, err := h.collection.InsertOne(context.Background(), data) + if err != nil { + return err + } + + return nil +} diff --git a/sentryflow-clients/mongo-client/go.mod b/sentryflow-clients/mongo-client/go.mod new file mode 100644 index 0000000..b639d95 --- /dev/null +++ b/sentryflow-clients/mongo-client/go.mod @@ -0,0 +1,29 @@ +module mongo-client + +go 1.19 + +replace sentryflow/protobuf => ../../protobuf + +require ( + go.mongodb.org/mongo-driver v1.13.1 + google.golang.org/grpc v1.61.1 + sentryflow/protobuf v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/protobuf v1.32.0 // indirect +) diff --git a/sentryflow-clients/mongo-client/go.sum b/sentryflow-clients/mongo-client/go.sum new file mode 100644 index 0000000..b583272 --- /dev/null +++ b/sentryflow-clients/mongo-client/go.sum @@ -0,0 +1,72 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/sentryflow-clients/mongo-client/main.go b/sentryflow-clients/mongo-client/main.go new file mode 100644 index 0000000..d41651f --- /dev/null +++ b/sentryflow-clients/mongo-client/main.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "fmt" + "google.golang.org/grpc" + "io" + "log" + "mongo-client/common" + "mongo-client/db" + "os" + protobuf "sentryflow/protobuf" +) + +func main() { + // Init DB + _, err := db.New() + if err != nil { + log.Fatalf("Unable to intialize DB: %v", err) + } + + // Load environment variables + cfg, err := common.LoadEnvVars() + if err != nil { + log.Fatalf("Could not load environment variables: %v", err) + } + + // Construct address and start listening + addr := fmt.Sprintf("%s:%d", cfg.ServerAddr, cfg.ServerPort) + + // Set up a connection to the server. + conn, err := grpc.Dial(addr, grpc.WithInsecure()) + if err != nil { + log.Fatalf("could not connect: %v", err) + } + defer conn.Close() + + // Start serving gRPC server + log.Printf("[gRPC] Successfully connected to %s", addr) + + // Create a client for the SentryFlow service. + client := protobuf.NewSentryFlowClient(conn) + + hostname, err := os.Hostname() + if err != nil { + log.Fatalf("could not find hostname: %v", err) + } + + // Define the client information. + clientInfo := &protobuf.ClientInfo{ + HostName: hostname, + } + + // Contact the server and print out its response. + stream, err := client.GetLog(context.Background(), clientInfo) + if err != nil { + log.Fatalf("could not get log: %v", err) + } + + for { + data, err := stream.Recv() + if err == io.EOF { + break + } + if err != nil { + log.Fatalf("failed to receive log: %v", err) + } + + err = db.Manager.InsertData(data) + if err != nil { + log.Printf("[DB] Failed to store data to DB: %v", err) + } + } +} diff --git a/sentryflow/.gitignore b/sentryflow/.gitignore new file mode 100644 index 0000000..62c8935 --- /dev/null +++ b/sentryflow/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/sentryflow/Dockerfile b/sentryflow/Dockerfile new file mode 100644 index 0000000..d6305aa --- /dev/null +++ b/sentryflow/Dockerfile @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: Apache-2.0 + +### Builder + +FROM golang:1.19-alpine3.17 as builder + +RUN apk --no-cache update +RUN apk add --no-cache git clang llvm make gcc protobuf make +RUN go install github.com/golang/protobuf/protoc-gen-go@latest +RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + +RUN mkdir /app +RUN mkdir /protobuf + +WORKDIR /protobuf +COPY /protobuf . +RUN go mod tidy +RUN make build + +WORKDIR /app +COPY /sentryflow . + +RUN go mod tidy +RUN go build -o sentryflow + +### Make executable image + +FROM alpine:3.18 as sentryflow + +RUN echo "@community http://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories + +RUN apk --no-cache update +RUN apk add bash + +COPY --from=builder /app/sentryflow . + +CMD ["./sentryflow"] diff --git a/sentryflow/Makefile b/sentryflow/Makefile new file mode 100644 index 0000000..738596d --- /dev/null +++ b/sentryflow/Makefile @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: Apache-2.0 + +IMAGE_NAME = 5gsec/sentryflow +TAG = v0.1 + + +.PHONY: build +build: + go mod tidy + go build -o sentryflow + +.PHONY: image +image: + docker build -t $(IMAGE_NAME):$(TAG) -f ./Dockerfile ../ + +.PHONY: clean +clean: + docker rmi $(IMAGE_NAME):$(TAG) + +.PHONY: run +run: + docker run -it --rm $(IMAGE_NAME):$(TAG) + +.PHONY: gofmt +gofmt: + cd $(CURDIR); gofmt -w -s -d $(shell find . -type f -name '*.go' -print) + +.PHONY: golint +golint: +ifeq (, $(shell which golint)) + @{ \ + set -e ;\ + GOLINT_TEMP_DIR=$$(mktemp -d) ;\ + cd $$GOLINT_TEMP_DIR ;\ + go mod init tmp ;\ + go get golang.org/x/lint/golint ;\ + go install golang.org/x/lint/golint ;\ + rm -rf $$GOLINT_TEMP_DIR ;\ + } +endif + cd $(CURDIR); golint ./... + +.PHONY: gosec +gosec: +ifeq (, $(shell which gosec)) + @{ \ + set -e ;\ + GOSEC_TEMP_DIR=$$(mktemp -d) ;\ + cd $$GOSEC_TEMP_DIR ;\ + go mod init tmp ;\ + go get github.com/securego/gosec/v2/cmd/gosec ;\ + go install github.com/securego/gosec/v2/cmd/gosec ;\ + rm -rf $$GOSEC_TEMP_DIR ;\ + } +endif + cd $(CURDIR); gosec -exclude=G402 ./... diff --git a/sentryflow/config/config.go b/sentryflow/config/config.go new file mode 100644 index 0000000..dd2ac60 --- /dev/null +++ b/sentryflow/config/config.go @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 + +package config + +import ( + "flag" + "fmt" + "log" + "os" + "strings" + + "github.com/spf13/viper" +) + +// NumbatConfig structure +type NumbatConfig struct { + OtelGRPCListenAddr string // IP address to use for OTEL gRPC + OtelGRPCListenPort string // Port to use for OTEL gRPC + + CustomExportListenAddr string // IP address to use for custom exporter gRPC + CustomExportListenPort string // Port to use for custom exporter gRPC + + PatchNamespace bool // Enable/Disable patching namespace for Istio injection + PatchRestartDeployments bool // Enable/Disable restarting deployments after patching + + Debug bool // Enable/Disable SentryFlow debug mode +} + +// GlobalCfg Global configuration for SentryFlow +var GlobalCfg NumbatConfig + +// Config const +const ( + OtelGRPCListenAddr string = "otelGRPCListenAddr" + OtelGRPCListenPort string = "otelGRPCListenPort" + CustomExportListenAddr string = "customExportListenAddr" + CustomExportListenPort string = "customExportListenPort" + PatchNamespace string = "patchNamespace" + PatchRestartDeployments string = "patchRestartDeployments" + Debug string = "debug" +) + +func readCmdLineParams() { + otelGRPCListenAddrStr := flag.String(OtelGRPCListenAddr, "0.0.0.0", "OTEL gRPC server listen address") + otelGRPCListenPortStr := flag.String(OtelGRPCListenPort, "4317", "OTEL gRPC server listen port") + customExportListenAddrStr := flag.String(CustomExportListenAddr, "0.0.0.0", "Custom export gRPC server listen address") + customExportListenPortStr := flag.String(CustomExportListenPort, "8080", "Custom export gRPC server listen port") + patchNamespaceB := flag.Bool(PatchNamespace, false, "Enable/Disable patching Istio injection to all namespaces") + patchRestartDeploymentsB := flag.Bool(PatchRestartDeployments, false, "Enable/Disable restarting deployments in all namespaces") + configDebugB := flag.Bool(Debug, false, "Enable/Disable debugging mode using logs") + + var flags []string + flag.VisitAll(func(f *flag.Flag) { + kv := fmt.Sprintf("%s:%v", f.Name, f.Value) + flags = append(flags, kv) + }) + log.Printf("Arguments [%s]", strings.Join(flags, " ")) + + flag.Parse() + + viper.SetDefault(OtelGRPCListenAddr, *otelGRPCListenAddrStr) + viper.SetDefault(OtelGRPCListenPort, *otelGRPCListenPortStr) + viper.SetDefault(CustomExportListenAddr, *customExportListenAddrStr) + viper.SetDefault(CustomExportListenPort, *customExportListenPortStr) + viper.SetDefault(PatchNamespace, *patchNamespaceB) + viper.SetDefault(PatchRestartDeployments, *patchRestartDeploymentsB) + viper.SetDefault(Debug, *configDebugB) +} + +// LoadConfig Load configuration +func LoadConfig() error { + // Read configuration from command line + readCmdLineParams() + + // Read environment variable, those are upper-cased + viper.AutomaticEnv() + + // todo: read configuration from config file + _ = os.Getenv("NUMBAT_CFG") + + GlobalCfg.OtelGRPCListenAddr = viper.GetString(OtelGRPCListenAddr) + GlobalCfg.OtelGRPCListenPort = viper.GetString(OtelGRPCListenPort) + GlobalCfg.CustomExportListenAddr = viper.GetString(CustomExportListenAddr) + GlobalCfg.CustomExportListenPort = viper.GetString(CustomExportListenPort) + GlobalCfg.PatchNamespace = viper.GetBool(PatchNamespace) + GlobalCfg.PatchRestartDeployments = viper.GetBool(PatchRestartDeployments) + GlobalCfg.Debug = viper.GetBool(Debug) + + log.Printf("Configuration [%+v]", GlobalCfg) + + return nil +} diff --git a/sentryflow/core/k8sHandler.go b/sentryflow/core/k8sHandler.go new file mode 100644 index 0000000..1ffae22 --- /dev/null +++ b/sentryflow/core/k8sHandler.go @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 + +package core + +import ( + "context" + "github.com/5GSEC/sentryflow/config" + "github.com/5GSEC/sentryflow/types" + "gopkg.in/yaml.v2" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "log" + "sync" + "time" +) + +// K8s global reference for Kubernetes Handler +var K8s *K8sHandler + +// init Function +func init() { + K8s = NewK8sHandler() +} + +// K8sHandler Structure +type K8sHandler struct { + config *rest.Config + clientSet *kubernetes.Clientset + + listWatchers map[string]*cache.ListWatch + informers map[string]cache.Controller + podMap map[string]*corev1.Pod // This map is NOT thread safe, meaning that race condition might occur + svcMap map[string]*corev1.Service // This map is NOT thread safe, meaning that race condition might occur +} + +// NewK8sHandler Function +func NewK8sHandler() *K8sHandler { + kh := &K8sHandler{ + listWatchers: make(map[string]*cache.ListWatch), + podMap: make(map[string]*corev1.Pod), + svcMap: make(map[string]*corev1.Service), + informers: make(map[string]cache.Controller), + } + + return kh +} + +// InitK8sClient Function +func (kh *K8sHandler) InitK8sClient() bool { + var err error + + // Initialize in cluster config + kh.config, err = rest.InClusterConfig() + if err != nil { + return false + } + + // Initialize Kubernetes clientSet + kh.clientSet, err = kubernetes.NewForConfig(kh.config) + if err != nil { + return false + } + + watchTargets := []string{"pods", "services"} + + // Look for existing resources in the cluster, create map + kh.initExistingResources() + + // Initialize watchers and informers for services and pods + // This will not run the informers yet + kh.initWatchers(watchTargets) + kh.initInformers() + + return true +} + +// initWatchers initializes watchers for pods and services in cluster +func (kh *K8sHandler) initWatchers(watchTargets []string) { + // Initialize watch for pods and services + for _, target := range watchTargets { + watcher := cache.NewListWatchFromClient( + kh.clientSet.CoreV1().RESTClient(), + target, + corev1.NamespaceAll, + fields.Everything(), + ) + kh.listWatchers[target] = watcher + } +} + +// initExistingResources will create a mapping table for existing services and pods into IPs +// This is required since informers are NOT going to see existing resources until they are updated, created or deleted +// Todo: Refactor this function, this is kind of messy +func (kh *K8sHandler) initExistingResources() { + // List existing Pods + podList, err := kh.clientSet.CoreV1().Pods(corev1.NamespaceAll).List(context.TODO(), v1.ListOptions{}) + if err != nil { + log.Print("Error listing Pods:", err.Error()) + } + + // Add existing Pods to the podMap + for _, pod := range podList.Items { + currentPod := pod + kh.podMap[pod.Status.PodIP] = ¤tPod + log.Printf("[K8s] Add existing pod %s: %s/%s", pod.Status.PodIP, pod.Namespace, pod.Name) + } + + // List existing Services + serviceList, err := kh.clientSet.CoreV1().Services(corev1.NamespaceAll).List(context.TODO(), v1.ListOptions{}) + if err != nil { + log.Print("Error listing Services:", err.Error()) + } + + // Add existing Services to the svcMap + for _, service := range serviceList.Items { + currentService := service // This will solve G601 for gosec + + // Check if the service has a LoadBalancer type + if service.Spec.Type == "LoadBalancer" { + for _, lbIngress := range service.Status.LoadBalancer.Ingress { + lbIP := lbIngress.IP + if lbIP != "" { + kh.svcMap[lbIP] = ¤tService + log.Printf("[K8s] Add existing service (LoadBalancer) %s: %s/%s", lbIP, service.Namespace, service.Name) + } + } + } else { + kh.svcMap[service.Spec.ClusterIP] = ¤tService + if len(service.Spec.ExternalIPs) != 0 { + for _, eIP := range service.Spec.ExternalIPs { + kh.svcMap[eIP] = ¤tService + log.Printf("[K8s] Add existing service %s: %s/%s", eIP, service.Namespace, service.Name) + } + } + } + } +} + +// initInformers initializes informers for services and pods in cluster +func (kh *K8sHandler) initInformers() { + // Create Pod controller informer + _, pc := cache.NewInformer( + kh.listWatchers["pods"], + &corev1.Pod{}, + time.Second*0, + cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { // Add pod information + pod := obj.(*corev1.Pod) + kh.podMap[pod.Status.PodIP] = pod + }, + UpdateFunc: func(oldObj, newObj interface{}) { // Update pod information + newPod := newObj.(*corev1.Pod) + kh.podMap[newPod.Status.PodIP] = newPod + }, + DeleteFunc: func(obj interface{}) { // Remove deleted pod information + pod := obj.(*corev1.Pod) + delete(kh.podMap, pod.Status.PodIP) + }, + }, + ) + + kh.informers["pods"] = pc + + // Create Service controller informer + _, sc := cache.NewInformer( + kh.listWatchers["services"], + &corev1.Service{}, + time.Second*0, + cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { // Add service information + service := obj.(*corev1.Service) + + if service.Spec.Type == "LoadBalancer" { + for _, lbIngress := range service.Status.LoadBalancer.Ingress { + lbIP := lbIngress.IP + if lbIP != "" { + kh.svcMap[lbIP] = service + } + } + } else { + kh.svcMap[service.Spec.ClusterIP] = service + if len(service.Spec.ExternalIPs) != 0 { + for _, eIP := range service.Spec.ExternalIPs { + kh.svcMap[eIP] = service + } + } + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { // Update service information + newService := newObj.(*corev1.Service) + if newService.Spec.Type == "LoadBalancer" { + for _, lbIngress := range newService.Status.LoadBalancer.Ingress { + lbIP := lbIngress.IP + if lbIP != "" { + kh.svcMap[lbIP] = newService + } + } + } else { + kh.svcMap[newService.Spec.ClusterIP] = newService + if len(newService.Spec.ExternalIPs) != 0 { + for _, eIP := range newService.Spec.ExternalIPs { + kh.svcMap[eIP] = newService + } + } + } + }, + DeleteFunc: func(obj interface{}) { + service := obj.(*corev1.Service) + if service.Spec.Type == "LoadBalancer" { + for _, lbIngress := range service.Status.LoadBalancer.Ingress { + lbIP := lbIngress.IP + if lbIP != "" { + delete(kh.svcMap, lbIP) + } + } + } else { + delete(kh.svcMap, service.Spec.ClusterIP) // Remove deleted service information + if len(service.Spec.ExternalIPs) != 0 { + for _, eIP := range service.Spec.ExternalIPs { + delete(kh.svcMap, eIP) + } + } + } + }, + }, + ) + + kh.informers["services"] = sc +} + +// RunInformers starts running informers +func (kh *K8sHandler) RunInformers(stopCh chan struct{}, wg *sync.WaitGroup) { + wg.Add(1) + for name, informer := range kh.informers { + name := name + informer := informer + go func() { + log.Printf("[K8s] Started informers for %s", name) + informer.Run(stopCh) + + defer wg.Done() + }() + } + + log.Printf("[K8s] Started all informers") +} + +// lookupIPAddress Function +func (kh *K8sHandler) lookupIPAddress(ipAddr string) interface{} { + // Look for pod map first + pod, ok := kh.podMap[ipAddr] + if ok { + return pod + } + + // Look for service map + service, ok := kh.svcMap[ipAddr] + if ok { + return service + } + + return nil +} + +// LookupNetworkedResource Function +func LookupNetworkedResource(srcIP string) types.K8sNetworkedResource { + ret := types.K8sNetworkedResource{ + Name: "Unknown", + Namespace: "Unknown", + Labels: make(map[string]string), + Type: types.K8sResourceTypeUnknown, + } + + // Find Kubernetes resource from source IP (service or a pod) + raw := K8s.lookupIPAddress(srcIP) + + // Currently supports Service or Pod + switch raw.(type) { + case *corev1.Pod: + pod, ok := raw.(*corev1.Pod) + if ok { + ret.Name = pod.Name + ret.Namespace = pod.Namespace + ret.Labels = pod.Labels + ret.Type = types.K8sResourceTypePod + } + case *corev1.Service: + svc, ok := raw.(*corev1.Service) + if ok { + ret.Name = svc.Name + ret.Namespace = svc.Namespace + ret.Labels = svc.Labels + ret.Type = types.K8sResourceTypeService + } + default: + ret.Type = types.K8sResourceTypeUnknown + } + + return ret +} + +// PatchIstioConfigMap patches the Istio's configmap for meshConfig +// This will make istio know that there is an exporter with envoyOtelAls +func (kh *K8sHandler) PatchIstioConfigMap() error { + // Get the ConfigMap istio-system/istio + configMap, err := kh.clientSet.CoreV1(). + ConfigMaps("istio-system"). + Get(context.Background(), "istio", v1.GetOptions{}) + if err != nil { + // Handle error + log.Fatalf("[Patcher] Unable to retrieve configmap istio-system/istio :%v", err) + return err + } + + // Define a map to represent the structure of the mesh configuration + var meshConfig map[string]interface{} + + // Unmarshal the YAML string into the map + meshConfigStr := configMap.Data["mesh"] + err = yaml.Unmarshal([]byte(meshConfigStr), &meshConfig) + if err != nil { + // Handle error + log.Fatalf("[Patcher] Unable to unmarshall configmap istio-system/istio :%v", err) + return err + } + + // Work with defaultProviders.accessLogs + dp, exists := meshConfig["defaultProviders"].(map[interface{}]interface{})["accessLogs"] + if !exists { // Add defaultProviders.accessLogs if it does not exist + meshConfig["defaultProviders"].(map[interface{}]interface{})["accessLogs"] = []string{"sentryflow"} + } else { // Just add a new entry sentryflow if it exists + dpSlice := dp.([]interface{}) // @todo find better solution for this + duplicate := false + for _, entry := range dpSlice { + if entry == "sentryflow" { + // If "sentryflow" already exists, do nothing + log.Printf("[Patcher] istio-system/istio ConfigMap has " + + "sentryflow under defaultProviders.accessLogs, ignoring... ") + duplicate = true + break + } + } + + // If "sentryflow" does not exist, append it + if !duplicate { + dpSlice = append(dpSlice, "sentryflow") + meshConfig["defaultProviders"].(map[interface{}]interface{})["accessLogs"] = dpSlice + } + } + + // ExtensionProvider for our service + eps := map[interface{}]interface{}{ + "name": "sentryflow", + "envoyOtelAls": map[interface{}]interface{}{ + "service": "sentryflow.sentryflow.svc.cluster.local", + "port": config.GlobalCfg.OtelGRPCListenPort, + }, + } + + // Work with extensionProviders + ep, exists := meshConfig["extensionProviders"] + if !exists { + // Create extensionProviders as a slice containing only the eps map + meshConfig["extensionProviders"] = []map[interface{}]interface{}{eps} + } else { + // Check if eps already exists in extensionProviders + epSlice, ok := ep.([]interface{}) + if !ok { + // handle the case where ep is not []interface{} + log.Printf("[Patcher] istio-system/istio ConfigMap extensionProviders has unexpected type") + } + + duplicate := false + for _, entry := range epSlice { + entryMap, ok := entry.(map[interface{}]interface{}) + if !ok { + // handle the case where an entry is not map[interface{}]interface{} + log.Printf("[Patcher] istio-system/istio ConfigMap extensionProviders entry has unexpected type") + } + if entryMap["name"] == eps["name"] { + // If "sentryflow" already exists, do nothing + log.Printf("[Patcher] istio-system/istio ConfigMap has sentryflow under extensionProviders, ignoring... ") + duplicate = true + break + } + } + + // Append eps to the existing slice + if !duplicate { + meshConfig["extensionProviders"] = append(ep.([]map[interface{}]interface{}), eps) + } + } + + // Update the ConfigMap data with the modified meshConfig + updatedMeshConfig, err := yaml.Marshal(meshConfig) + if err != nil { + // Handle error + log.Fatalf("[Patcher] Unable to marshal updated meshConfig to YAML: %v", err) + return err + } + + // Convert the []byte to string + configMap.Data["mesh"] = string(updatedMeshConfig) + + // Preview changes, for debugging + if config.GlobalCfg.Debug { + log.Printf("[PATCH] Patching istio-system/istio ConfigMap as: \n%v", configMap) + } + + // Patch the ConfigMap back to the cluster + updatedConfigMap, err := kh.clientSet.CoreV1(). + ConfigMaps("istio-system"). + Update(context.Background(), configMap, v1.UpdateOptions{}) + if err != nil { + // Handle error + log.Fatalf("[Patcher] Unable to update configmap istio-system/istio :%v", err) + return err + } + + // Update successful + if config.GlobalCfg.Debug { + log.Printf("[Patcher] Updated istio-system/istio ConfigMap as: \n%v", updatedConfigMap) + } + return nil +} + +// PatchNamespaces patches namespaces for adding istio injection +func (kh *K8sHandler) PatchNamespaces() error { + // Get the list of namespaces + namespaces, err := kh.clientSet.CoreV1().Namespaces().List(context.Background(), v1.ListOptions{}) + if err != nil { + // Handle error + log.Fatalf("[Patcher] Unable to list namespaces: %v", err) + return err + } + + // Loop through each namespace and update it with the desired labels + // @todo make this skip adding labeles to namespaces which are defined in the config + for _, ns := range namespaces.Items { + currentNs := ns + + // We are not going to inject sidecars to sentryflow namespace + if currentNs.Name == "sentryflow" { + continue + } + + // Add istio-injection="enabled" for namespaces + currentNs.Labels["istio-injection"] = "enabled" + + // Update the namespace in the cluster + updatedNamespace, err := kh.clientSet.CoreV1().Namespaces().Update(context.TODO(), ¤tNs, v1.UpdateOptions{ + FieldManager: "patcher", + }) + if err != nil { + log.Printf("[Patcher] Unable to update namespace %s: %v", currentNs.Name, err) + return err + } + + log.Printf("[Patcher] Updated Namespace: %s\n", updatedNamespace.Name) + } + + return nil +} + +// PatchRestartDeployments restarts the deployments in namespaces which were applied with "istio-injection": "enabled" +func (kh *K8sHandler) PatchRestartDeployments() error { + // Get the list of all deployments in all namespaces + deployments, err := kh.clientSet.AppsV1().Deployments("").List(context.Background(), v1.ListOptions{}) + if err != nil { + // Handle error + log.Fatalf("[Patcher] Unable to list deployments: %v", err) + return err + } + + // Iterate over each deployment and restart it + for _, deployment := range deployments.Items { + // We are not going to inject sidecars to sentryflow namespace + if deployment.Namespace == "sentryflow" { + continue + } + + // Restart the deployment + err := kh.restartDeployment(deployment.Namespace, deployment.Name) + if err != nil { + // Handle error + log.Printf("[Patcher] Unable to restart deployment %s/%s: %v", deployment.Namespace, deployment.Name, err) + continue + } + + log.Printf("[Patcher] Deployment %s/%s restarted", deployment.Namespace, deployment.Name) + } + + return nil +} + +// restartDeployment performs a rolling restart for a deployment in the specified namespace +// @todo: fix this, this DOES NOT restart deployments +func (kh *K8sHandler) restartDeployment(namespace string, deploymentName string) error { + deploymentClient := kh.clientSet.AppsV1().Deployments(namespace) + + // Get the deployment to retrieve the current spec + deployment, err := deploymentClient.Get(context.Background(), deploymentName, v1.GetOptions{}) + if err != nil { + return err + } + + // Trigger a rolling restart by updating the deployment's labels or annotations + deployment.Spec.Template.ObjectMeta.Labels["restartedAt"] = v1.Now().String() + + // Update the deployment to trigger the rolling restart + _, err = deploymentClient.Update(context.TODO(), deployment, v1.UpdateOptions{}) + if err != nil { + return err + } + + return nil +} diff --git a/sentryflow/core/logHandler.go b/sentryflow/core/logHandler.go new file mode 100644 index 0000000..4be0cd1 --- /dev/null +++ b/sentryflow/core/logHandler.go @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: Apache-2.0 + +package core + +import ( + "github.com/5GSEC/sentryflow/exporter" + "github.com/5GSEC/sentryflow/metrics" + "github.com/5GSEC/sentryflow/protobuf" + "github.com/5GSEC/sentryflow/types" + "log" + "strconv" + "strings" + "sync" +) + +// Lh global reference for LogHandler +var Lh *LogHandler + +// init Function +func init() { + Lh = NewLogHandler() +} + +// LogHandler Structure +type LogHandler struct { + stopChan chan struct{} + logChan chan interface{} +} + +// NewLogHandler Structure +func NewLogHandler() *LogHandler { + lh := &LogHandler{ + stopChan: make(chan struct{}), + logChan: make(chan interface{}), + } + + return lh +} + +// StartLogProcessor Function +func StartLogProcessor(wg *sync.WaitGroup) { + go Lh.logProcessingRoutine(wg) +} + +// StopLogProcessor Function +func StopLogProcessor() { + Lh.stopChan <- struct{}{} +} + +// InsertLog Function +func (lh *LogHandler) InsertLog(data interface{}) { + lh.logChan <- data +} + +// logProcessingRoutine Function +func (lh *LogHandler) logProcessingRoutine(wg *sync.WaitGroup) { + wg.Add(1) + for { + select { + case l, ok := <-lh.logChan: + if !ok { + log.Printf("[Error] Unable to process log") + } + + // Check new log's type + switch l.(type) { + case *protobuf.APILog: + go processAccessLog(l.(*protobuf.APILog)) + } + + case <-lh.stopChan: + wg.Done() + return + } + } +} + +// processAccessLog Function +func processAccessLog(al *protobuf.APILog) { + // Send AccessLog to exporter first + exporter.InsertAccessLog(al) + + // Then send AccessLog to metrics + metrics.InsertAccessLog(al) +} + +// GenerateAccessLogs Function +func GenerateAccessLogs(logText string) []*protobuf.APILog { + // @todo this needs more optimization, this code is kind of messy + // Create an array of AccessLogs for returning gRPC comm + var index int + ret := make([]*protobuf.APILog, 0) + + // Preprocess redundant chars + logText = strings.ReplaceAll(logText, `\"`, "") + logText = strings.ReplaceAll(logText, `}`, "") + + // Split logs by log_records, this is single access log instance + parts := strings.Split(logText, "log_records") + if len(parts) == 0 { + return nil + } + + // Ignore the first entry, this was the metadata "resource_logs:{resource:{ scope_logs:{" part. + for _, al := range parts[0:] { + if len(al) == 0 { + continue + } + + index = strings.Index(al, "string_value:\"") + if index == -1 { + continue + } + + result := al[index+len("string_value:\""):] + words := strings.Fields(result) + + method := words[1] + path := words[2] + protocolName := words[3] + timeStamp := words[0] + resCode, _ := strconv.ParseInt(words[4], 10, 64) + + srcInform := words[21] + dstInform := words[20] + + var srcIP string + var dstIP string + var srcPort string + var dstPort string + var colonIndex int + + // Extract the left and right words based on the colon delimiter (ADDR:PORT) + colonIndex = strings.LastIndex(srcInform, ":") + if colonIndex > 0 && colonIndex < len(srcInform)-1 { + srcIP = strings.TrimSpace(srcInform[:colonIndex]) + srcPort = strings.TrimSpace(srcInform[colonIndex+1:]) + } + + colonIndex = strings.LastIndex(dstInform, ":") + if colonIndex > 0 && colonIndex < len(dstInform)-1 { + dstIP = strings.TrimSpace(dstInform[:colonIndex]) + dstPort = strings.TrimSpace(dstInform[colonIndex+1:]) + } + + // Lookup using K8s API + src := LookupNetworkedResource(srcIP) + dst := LookupNetworkedResource(dstIP) + + // Create AccessLog in our gRPC format + cur := protobuf.APILog{ + TimeStamp: timeStamp, + Id: 0, // do 0 for now, we are going to write it later + SrcNamespace: src.Namespace, + SrcName: src.Name, + SrcLabel: src.Labels, + SrcIP: srcIP, + SrcPort: srcPort, + SrcType: types.K8sResourceTypeToString(src.Type), + DstNamespace: dst.Namespace, + DstName: dst.Name, + DstLabel: dst.Labels, + DstIP: dstIP, + DstPort: dstPort, + DstType: types.K8sResourceTypeToString(dst.Type), + Protocol: protocolName, + Method: method, + Path: path, + ResponseCode: int32(resCode), + } + + ret = append(ret, &cur) + } + + return ret +} diff --git a/sentryflow/core/otelHandler.go b/sentryflow/core/otelHandler.go new file mode 100644 index 0000000..851328e --- /dev/null +++ b/sentryflow/core/otelHandler.go @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: Apache-2.0 + +package core + +import ( + "context" + "errors" + "fmt" + cfg "github.com/5GSEC/sentryflow/config" + otelLogs "go.opentelemetry.io/proto/otlp/collector/logs/v1" + "google.golang.org/grpc" + "log" + "net" + "sync" +) + +// Oh Global reference for OtelHandler +var Oh *OtelHandler +var olh *OtelLogServer + +// init Function +func init() { + Oh = NewOtelHandler() + olh = NewOtelLogServer() +} + +// OtelHandler Structure +type OtelHandler struct { + stopChan chan struct{} + + listener net.Listener + gRPCServer *grpc.Server +} + +// NewOtelHandler Function +func NewOtelHandler() *OtelHandler { + oh := &OtelHandler{ + stopChan: make(chan struct{}), + } + + return oh +} + +// InitOtelServer Function +func (oh *OtelHandler) InitOtelServer() error { + listenAddr := fmt.Sprintf("%s:%s", cfg.GlobalCfg.OtelGRPCListenAddr, cfg.GlobalCfg.OtelGRPCListenPort) + + // Start listening + lis, err := net.Listen("tcp", listenAddr) + if err != nil { + msg := fmt.Sprintf("unable to listen at %s: %v", listenAddr, err) + return errors.New(msg) + } + + // Create gRPC Server, register services + server := grpc.NewServer() + otelLogs.RegisterLogsServiceServer(server, olh) + + oh.listener = lis + oh.gRPCServer = server + + log.Printf("[OpenTelemetry] Server Listening at %s", listenAddr) + return nil +} + +// StartOtelServer Function +func (oh *OtelHandler) StartOtelServer(wg *sync.WaitGroup) error { + log.Printf("[OpenTelemetry] Starting server") + var err error + err = nil + + // Serve is blocking function + go func() { + wg.Add(1) + err = oh.gRPCServer.Serve(oh.listener) + if err != nil { + wg.Done() + return + } + + wg.Done() + }() + + return err +} + +// StopOtelServer Function +func (oh *OtelHandler) StopOtelServer() { + // Gracefully cleanup + oh.stopChan <- struct{}{} + + // Gracefully stop gRPC Server + oh.gRPCServer.GracefulStop() + + log.Printf("[OpenTelemetry] Stopped server") +} + +// OtelLogServer structure +type OtelLogServer struct { + otelLogs.UnimplementedLogsServiceServer +} + +// NewOtelLogServer Function +func NewOtelLogServer() *OtelLogServer { + return new(OtelLogServer) +} + +// Export Function +func (ols *OtelLogServer) Export(_ context.Context, req *otelLogs.ExportLogsServiceRequest) (*otelLogs.ExportLogsServiceResponse, error) { + // This is for Log.Export in OpenTelemetry format + als := GenerateAccessLogs(req.String()) + + for _, al := range als { + Lh.InsertLog(al) + } + + // For now, we will not consider partial success + ret := otelLogs.ExportLogsServiceResponse{ + PartialSuccess: nil, + } + + return &ret, nil +} diff --git a/sentryflow/core/sentryflow.go b/sentryflow/core/sentryflow.go new file mode 100644 index 0000000..ce6b901 --- /dev/null +++ b/sentryflow/core/sentryflow.go @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: Apache-2.0 + +package core + +import ( + cfg "github.com/5GSEC/sentryflow/config" + "github.com/5GSEC/sentryflow/exporter" + "github.com/5GSEC/sentryflow/metrics" + "log" + "sync" +) + +// StopChan Channel +var StopChan chan struct{} + +// init Function +func init() { + StopChan = make(chan struct{}) +} + +// NumbatDaemon Structure +type NumbatDaemon struct { + WgDaemon *sync.WaitGroup +} + +// NewNumbatDaemon Function +func NewNumbatDaemon() *NumbatDaemon { + dm := new(NumbatDaemon) + + dm.WgDaemon = new(sync.WaitGroup) + + return dm +} + +// DestroyNumbatDaemon Function +func (dm *NumbatDaemon) DestroyNumbatDaemon() { + +} + +// watchK8s Function +func (dm *NumbatDaemon) watchK8s() { + K8s.RunInformers(StopChan, dm.WgDaemon) +} + +// logProcessor Function +func (dm *NumbatDaemon) logProcessor() { + StartLogProcessor(dm.WgDaemon) + log.Printf("[SentryFlow] Started log processor") +} + +// metricAnalyzer Function +func (dm *NumbatDaemon) metricAnalyzer() { + metrics.StartMetricsAnalyzer(dm.WgDaemon) + log.Printf("[SentryFlow] Started metric analyzer") +} + +// otelServer Function +func (dm *NumbatDaemon) otelServer() { + // Initialize and start OpenTelemetry Server + err := Oh.InitOtelServer() + if err != nil { + log.Fatalf("[SentryFlow] Unable to intialize OpenTelemetry Server: %v", err) + return + } + + err = Oh.StartOtelServer(dm.WgDaemon) + if err != nil { + log.Fatalf("[SentryFlow] Unable to start OpenTelemetry Server: %v", err) + return + } + + log.Printf("[SentryFlow] Started OpenTelemetry collector") +} + +// exporterServer Function +func (dm *NumbatDaemon) exporterServer() { + // Initialize and start exporter server + err := exporter.Exp.InitExporterServer() + if err != nil { + log.Fatalf("[SentryFlow] Unable to initialize Exporter Server: %v", err) + return + } + + err = exporter.Exp.StartExporterServer(dm.WgDaemon) + if err != nil { + log.Fatalf("[SentryFlow] Unable to start Exporter Server: %v", err) + } + log.Printf("[SentryFlow] Initialized exporter") +} + +// patchK8s Function +func (dm *NumbatDaemon) patchK8s() error { + err := K8s.PatchIstioConfigMap() + if err != nil { + return err + } + + if cfg.GlobalCfg.PatchNamespace { + err = K8s.PatchNamespaces() + if err != nil { + return err + } + } + + if cfg.GlobalCfg.PatchRestartDeployments { + err = K8s.PatchRestartDeployments() + if err != nil { + return err + } + } + + return nil +} + +// SentryFlow Function +func SentryFlow() { + // create a daemon + dm := NewNumbatDaemon() + + // Initialize Kubernetes client + if !K8s.InitK8sClient() { + log.Printf("[Error] Failed to initialize Kubernetes client") + dm.DestroyNumbatDaemon() + return + } + + log.Printf("[SentryFlow] Initialized Kubernetes client") + + dm.watchK8s() + log.Printf("[SentryFlow] Started to monitor Kubernetes resources") + + if dm.patchK8s() != nil { + log.Printf("[SentryFlow] Failed to patch Kubernetes") + } + log.Printf("[SentryFlow] Patched Kubernetes and Istio configuration") + + // Start log processor + dm.logProcessor() + + // Start metric analyzer + dm.metricAnalyzer() + + // Start OpenTelemetry server + dm.otelServer() + + // Start exporter server + dm.exporterServer() + + log.Printf("[SentryFlow] Successfully started SentryFlow") + dm.WgDaemon.Wait() +} diff --git a/sentryflow/exporter/exporterHandler.go b/sentryflow/exporter/exporterHandler.go new file mode 100644 index 0000000..10d43a6 --- /dev/null +++ b/sentryflow/exporter/exporterHandler.go @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: Apache-2.0 + +package exporter + +import ( + "errors" + "fmt" + cfg "github.com/5GSEC/sentryflow/config" + "github.com/5GSEC/sentryflow/protobuf" + "net" + "sync" + "time" + + "github.com/emicklei/go-restful/v3/log" + "google.golang.org/grpc" +) + +// Exp global reference for Exporter Handler +var Exp *Handler + +// init Function +func init() { + Exp = NewExporterHandler() +} + +// Handler structure +type Handler struct { + baseExecutionID uint64 + currentLogCount uint64 + logChannel chan *protobuf.APILog + lock sync.Mutex // @todo find better solution for this + stopChan chan struct{} + + exporters []*Inform + exporterLock sync.Mutex + exporterLogs chan *protobuf.APILog + + listener net.Listener + gRPCServer *grpc.Server +} + +// Inform structure +type Inform struct { + stream protobuf.SentryFlow_GetLogServer + error chan error + Hostname string + IPAddress string +} + +// NewExporterHandler Function +func NewExporterHandler() *Handler { + exp := &Handler{ + baseExecutionID: uint64(time.Now().UnixMicro()), + currentLogCount: 0, + exporters: make([]*Inform, 0), + logChannel: make(chan *protobuf.APILog), + stopChan: make(chan struct{}), + lock: sync.Mutex{}, + exporterLock: sync.Mutex{}, + exporterLogs: make(chan *protobuf.APILog), + } + + return exp +} + +// InsertAccessLog Function +func InsertAccessLog(al *protobuf.APILog) { + // Avoid race condition for currentLogCount, otherwise we might have duplicate IDs + Exp.lock.Lock() + al.Id = Exp.baseExecutionID + Exp.currentLogCount + Exp.currentLogCount++ + Exp.lock.Unlock() + + Exp.exporterLogs <- al +} + +// InitExporterServer Function +func (exp *Handler) InitExporterServer() error { + listenAddr := fmt.Sprintf("%s:%s", cfg.GlobalCfg.CustomExportListenAddr, cfg.GlobalCfg.CustomExportListenPort) + + // Start listening + lis, err := net.Listen("tcp", listenAddr) + if err != nil { + msg := fmt.Sprintf("unable to listen at %s: %v", listenAddr, err) + return errors.New(msg) + } + + // Create gRPC server + server := grpc.NewServer() + protobuf.RegisterSentryFlowServer(server, exs) + + exp.listener = lis + exp.gRPCServer = server + + log.Printf("[Exporter] Exporter listening at %s", listenAddr) + return nil +} + +// StartExporterServer Function +func (exp *Handler) StartExporterServer(wg *sync.WaitGroup) error { + log.Printf("[Exporter] Starting exporter server") + var err error + err = nil + + go exp.exportRoutine(wg) + + go func() { + wg.Add(1) + // Serve is blocking function + err = exp.gRPCServer.Serve(exp.listener) + if err != nil { + wg.Done() + return + } + + wg.Done() + }() + + return err +} + +// exportRoutine Function +func (exp *Handler) exportRoutine(wg *sync.WaitGroup) { + wg.Add(1) + log.Printf("[Exporter] Starting export routine") + +routineLoop: + for { + select { + // @todo add more channels for this + case al, ok := <-exp.exporterLogs: + if !ok { + log.Printf("[Exporter] Log exporter channel closed") + break routineLoop + } + + err := exp.sendLogs(al) + if err != nil { + log.Printf("[Exporter] Log exporting failed %v:", err) + } + + case <-exp.stopChan: + break routineLoop + } + } + + defer wg.Done() + return +} + +// sendLogs Function +func (exp *Handler) sendLogs(l *protobuf.APILog) error { + exp.exporterLock.Lock() + defer exp.exporterLock.Unlock() + + // iterate and send logs + failed := 0 + total := len(exp.exporters) + for _, exporter := range exp.exporters { + curRetry := 0 + + // @todo: make max retry count per logs using config + // @todo: make max retry count per single exporter before removing the exporter using config + var err error + for curRetry < 3 { + err = exporter.stream.Send(l) + if err != nil { + log.Printf("[Exporter] Unable to send log to %s(%s) retry=%d/%d: %v", + exporter.Hostname, exporter.IPAddress, curRetry, 3, err) + curRetry++ + } else { + break + } + } + + // Count failed + if err != nil { + failed++ + } + } + + // notify failed count + if failed != 0 { + msg := fmt.Sprintf("unable to send logs properly %d/%d failed", failed, total) + return errors.New(msg) + } + + return nil +} + +// StopExporterServer Function +func (exp *Handler) StopExporterServer() { + // Gracefully stop all client connections + exp.stopChan <- struct{}{} + + // Gracefully stop gRPC Server + exp.gRPCServer.GracefulStop() + + log.Printf("[Exporter] Stopped exporter server") +} diff --git a/sentryflow/exporter/exporterServer.go b/sentryflow/exporter/exporterServer.go new file mode 100644 index 0000000..868648f --- /dev/null +++ b/sentryflow/exporter/exporterServer.go @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 + +package exporter + +import ( + "context" + metricAPI "github.com/5GSEC/sentryflow/metrics/api" + "github.com/5GSEC/sentryflow/protobuf" + "log" +) + +var exs *Server + +// init Function +func init() { + exs = NewExporterServer() +} + +// Server Structure +type Server struct { + protobuf.UnimplementedSentryFlowServer // @todo: make this fixed. +} + +// NewExporterServer Function +func NewExporterServer() *Server { + return new(Server) +} + +// GetLog Function +func (exs *Server) GetLog(info *protobuf.ClientInfo, stream protobuf.SentryFlow_GetLogServer) error { + log.Printf("[Exporter] Client %s(%s) connected", info.HostName, info.IPAddress) + + curExporter := &Inform{ + stream: stream, + Hostname: info.HostName, + IPAddress: info.IPAddress, + } + + // Append new exporter client for future use + Exp.exporterLock.Lock() + Exp.exporters = append(Exp.exporters, curExporter) + Exp.exporterLock.Unlock() + + // Keeping gRPC stream alive + // refer https://stackoverflow.com/questions/36921131/ + return <-curExporter.error +} + +// GetAPIMetrics Function +func (exs *Server) GetAPIMetrics(_ context.Context, info *protobuf.ClientInfo) (*protobuf.APIMetric, error) { + log.Printf("[Exporter] Client %s(%s) connected", info.HostName, info.IPAddress) + + // Construct protobuf return value + ret := protobuf.APIMetric{ + PerAPICounts: metricAPI.GetPerAPICount(), + } + + return &ret, nil +} diff --git a/sentryflow/go.mod b/sentryflow/go.mod new file mode 100644 index 0000000..d863eaa --- /dev/null +++ b/sentryflow/go.mod @@ -0,0 +1,75 @@ +module github.com/5GSEC/sentryflow + +go 1.21 + +toolchain go1.22.0 + +replace github.com/5GSEC/sentryflow/protobuf => ../protobuf + +require ( + github.com/5GSEC/sentryflow/protobuf v0.0.0-00010101000000-000000000000 + github.com/emicklei/go-restful/v3 v3.11.0 + github.com/spf13/viper v1.18.2 + go.opentelemetry.io/proto/otlp v1.0.0 + google.golang.org/grpc v1.61.1 + gopkg.in/yaml.v2 v2.4.0 + k8s.io/api v0.29.0 + k8s.io/apimachinery v0.29.0 + k8s.io/client-go v0.29.0 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.17.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/sentryflow/go.sum b/sentryflow/go.sum new file mode 100644 index 0000000..835e8a6 --- /dev/null +++ b/sentryflow/go.sum @@ -0,0 +1,217 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= +k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/sentryflow/main.go b/sentryflow/main.go new file mode 100644 index 0000000..a3b6381 --- /dev/null +++ b/sentryflow/main.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + cfg "github.com/5GSEC/sentryflow/config" + core "github.com/5GSEC/sentryflow/core" + _ "google.golang.org/grpc/encoding/gzip" // If not set, encoding problem occurs https://stackoverflow.com/questions/74062727 + "log" +) + +// main is the entrypoint of this program +func main() { + err := cfg.LoadConfig() + if err != nil { + log.Fatalf("[SentryFlow] Unable to load config: %v", err) + } + + core.SentryFlow() +} diff --git a/sentryflow/metrics/api/aiHandler.go b/sentryflow/metrics/api/aiHandler.go new file mode 100644 index 0000000..3e5844b --- /dev/null +++ b/sentryflow/metrics/api/aiHandler.go @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 + +package api + +// ah Local reference for AI handler server +var ah *aiHandler + +// init Function +func init() { + +} + +// aiHandler Structure +type aiHandler struct { + aiHost string + aiPort string + + // @todo: add gRPC stream here for bidirectional connection +} + +// newAIHandler Function +func newAIHandler(host string, port string) *aiHandler { + ah := &aiHandler{ + aiHost: host, + aiPort: port, + } + + return ah +} + +// initHandler Function +func (ah *aiHandler) initHandler() error { + return nil +} + +// callAI Function +func (ah *aiHandler) callAI(api string) error { + // @todo: add gRPC send request + return nil +} + +// processBatch Function +func processBatch(batch []string, update bool) error { + for _, _ = range batch { + + } + + return nil +} + +// performHealthCheck Function +func (ah *aiHandler) performHealthCheck() error { + return nil +} + +// disconnect Function +func (ah *aiHandler) disconnect() { + return +} diff --git a/sentryflow/metrics/api/apiAnalyzer.go b/sentryflow/metrics/api/apiAnalyzer.go new file mode 100644 index 0000000..0798746 --- /dev/null +++ b/sentryflow/metrics/api/apiAnalyzer.go @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "sync" +) + +// aa Local reference for API analyzer +var aa *Analyzer + +// init function +func init() { + aa = NewAPIAnalyzer() +} + +// Analyzer Structure +type Analyzer struct { + perAPICount map[string]uint64 + perAPICountLock sync.Mutex // @todo perhaps combine those two? + + stopChan chan struct{} + apiJob chan string +} + +// NewAPIAnalyzer Function +func NewAPIAnalyzer() *Analyzer { + ret := &Analyzer{ + perAPICount: make(map[string]uint64), + } + + return ret +} + +// StartAPIAnalyzer Function +func StartAPIAnalyzer(wg *sync.WaitGroup) { + go apiAnalyzerRoutine(wg) +} + +// StopAPIAnalyzer Function +func StopAPIAnalyzer() { + aa.stopChan <- struct{}{} +} + +// apiAnalyzerRoutine Function +func apiAnalyzerRoutine(wg *sync.WaitGroup) { + wg.Add(1) + for { + select { + case job, ok := <-aa.apiJob: + if !ok { + // @todo perhaps error message here? + continue + } + analyzeAPI(job) + + case <-aa.stopChan: + wg.Done() + break + } + } +} + +// analyzeAPI Function +func analyzeAPI(api string) { + // @todo implement this + classifyAPI(api) +} + +// GetPerAPICount Function +func GetPerAPICount() map[string]uint64 { + aa.perAPICountLock.Lock() + ret := aa.perAPICount + aa.perAPICountLock.Unlock() + + return ret +} + +// UpdatePerAPICount Function +func UpdatePerAPICount(nm map[string]uint64) { + aa.perAPICountLock.Lock() + aa.perAPICount = nm + aa.perAPICountLock.Unlock() +} + +// InsertAnalyzeJob Function +func InsertAnalyzeJob(api string) { + aa.apiJob <- api +} diff --git a/sentryflow/metrics/api/apiClassifier.go b/sentryflow/metrics/api/apiClassifier.go new file mode 100644 index 0000000..e251dc1 --- /dev/null +++ b/sentryflow/metrics/api/apiClassifier.go @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 + +package api + +type node struct { + path string + count int + child []*node +} + +type classifiedAPI struct { + destination string + method string + URIRoot *node +} + +// classifyAPI Function +func classifyAPI(api string) { +} + +// generateMetric Function +func generateMetric(cal classifiedAPI) { + +} + +// statisticOfAPIsPerDestination Function +func statisticOfAPIsPerDestination(cal classifiedAPI) { + +} + +// statisticOfAPIsPerMin Function +func statisticOfAPIsPerMin(cal classifiedAPI) { + +} + +// statisticOfErrorAPI Function +func statisticOfErrorAPI(cal classifiedAPI) { + +} + +// statisticOfAPILatency Function +func statisticOfAPILatency(cal classifiedAPI) { + +} diff --git a/sentryflow/metrics/metricHandler.go b/sentryflow/metrics/metricHandler.go new file mode 100644 index 0000000..82c5c44 --- /dev/null +++ b/sentryflow/metrics/metricHandler.go @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +import ( + "github.com/5GSEC/sentryflow/metrics/api" + "github.com/5GSEC/sentryflow/protobuf" + "sync" +) + +// Mh Global reference for metric handler +var Mh *MetricHandler + +// init Function +func init() { + Mh = NewMetricHandler() +} + +// MetricHandler Structure +type MetricHandler struct { +} + +// NewMetricHandler Function +func NewMetricHandler() *MetricHandler { + mh := &MetricHandler{} + + return mh +} + +// StartMetricsAnalyzer Function +func StartMetricsAnalyzer(wg *sync.WaitGroup) { + api.StartAPIAnalyzer(wg) +} + +// StopMetricsAnalyzer Function +func StopMetricsAnalyzer() { + api.StopAPIAnalyzer() +} + +// InsertAccessLog Function +func InsertAccessLog(al *protobuf.APILog) { + // @todo: make this fixed, for now will just send path from AccessLog + api.InsertAnalyzeJob(al.Path) +} diff --git a/sentryflow/types/k8sResources.go b/sentryflow/types/k8sResources.go new file mode 100644 index 0000000..603efaa --- /dev/null +++ b/sentryflow/types/k8sResources.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package types + +// k8sResources const +const ( + K8sResourceTypeUnknown = 0 + K8sResourceTypePod = 1 + K8sResourceTypeService = 2 +) + +// K8sNetworkedResource Structure +type K8sNetworkedResource struct { + Name string + Namespace string + Labels map[string]string + Type uint8 +} + +// K8sResourceTypeToString Function +func K8sResourceTypeToString(t uint8) string { + switch t { + case K8sResourceTypePod: + return "Pod" + case K8sResourceTypeService: + return "Service" + case K8sResourceTypeUnknown: + default: + return "Unknown" + } + + return "Unknown" +}