├── .devcontainer ├── devcontainer.json └── post-install.sh ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── enhancement.md │ └── failing-tests.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── docker-image-build.yml │ ├── docker-image.yml │ ├── lint.yml │ ├── release.yml │ ├── test-e2e.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── LICENSE ├── Makefile ├── OWNERS ├── PROJECT ├── README.md ├── api ├── artifact │ └── v1alpha1 │ │ ├── config_types.go │ │ ├── groupversion_info.go │ │ ├── plugin_types.go │ │ ├── rulesfile_types.go │ │ └── zz_generated.deepcopy.go ├── common │ └── v1alpha1 │ │ ├── types.go │ │ └── zz_generated.deepcopy.go └── instance │ └── v1alpha1 │ ├── falco_types.go │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── build └── Dockerfile ├── cmd ├── artifact │ ├── doc.go │ └── main.go └── falco │ ├── doc.go │ └── main.go ├── config ├── crd │ ├── bases │ │ ├── artifact.falcosecurity.dev_configs.yaml │ │ ├── artifact.falcosecurity.dev_plugins.yaml │ │ ├── artifact.falcosecurity.dev_rulesfiles.yaml │ │ └── instance.falcosecurity.dev_falcos.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── default │ └── kustomization.yaml ├── dist │ └── install.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml └── samples │ ├── artifact_v1alpha1_config.yaml │ ├── artifact_v1alpha1_plugin.yaml │ ├── artifact_v1alpha1_rulesfile.yaml │ ├── instance_v1alpha1_falco.yaml │ └── kustomization.yaml ├── controllers ├── artifact │ ├── config │ │ ├── controller.go │ │ ├── controller_test.go │ │ ├── doc.go │ │ └── suite_test.go │ ├── plugin │ │ ├── controller.go │ │ ├── controller_test.go │ │ ├── doc.go │ │ └── suite_test.go │ └── rulesfile │ │ ├── controller.go │ │ ├── controller_test.go │ │ ├── doc.go │ │ └── suite_test.go └── falco │ ├── clusterRole.go │ ├── clusterRoleBinding.go │ ├── clusterRoleBinding_test.go │ ├── clusterRole_test.go │ ├── conditions.go │ ├── conditions_test.go │ ├── configmap.go │ ├── configmap_test.go │ ├── controller.go │ ├── controller_test.go │ ├── defaults.go │ ├── diff.go │ ├── diff_test.go │ ├── doc.go │ ├── mockClient.go │ ├── nameGenerator.go │ ├── nameGenerator_test.go │ ├── resourceApplyConfiguration.go │ ├── resourceGenerator.go │ ├── resourceGenerator_test.go │ ├── resourceHandlers.go │ ├── resourceHandlers_test.go │ ├── role.go │ ├── roleBinding.go │ ├── roleBinding_test.go │ ├── role_test.go │ ├── service.go │ ├── serviceAccount.go │ ├── serviceAccount_test.go │ ├── service_test.go │ └── suite_test.go ├── docs └── images │ └── falco-operator-architecture.svg ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt └── update-kube-static-scheme-parser.sh ├── internal └── pkg │ ├── artifact │ ├── doc.go │ └── manager.go │ ├── common │ ├── archive.go │ ├── doc.go │ ├── finalizer.go │ └── sidecar.go │ ├── controllerhelper │ ├── deletion.go │ ├── node.go │ └── node_test.go │ ├── credentials │ ├── doc.go │ └── secret.go │ ├── image │ ├── const.go │ ├── doc.go │ ├── image.go │ └── image_test.go │ ├── mounts │ ├── consts.go │ └── doc.go │ ├── oci │ ├── client │ │ ├── client.go │ │ └── doc.go │ └── puller │ │ ├── doc.go │ │ ├── puller.go │ │ └── types.go │ ├── priority │ ├── doc.go │ └── priority.go │ ├── scheme │ └── parser.go │ └── version │ └── version.go └── test ├── e2e ├── e2e_suite_test.go └── e2e_test.go └── utils ├── doc.go └── utils.go /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kubebuilder DevContainer", 3 | "image": "golang:1.23", 4 | "features": { 5 | "ghcr.io/devcontainers/features/docker-in-docker:2": {}, 6 | "ghcr.io/devcontainers/features/git:1": {} 7 | }, 8 | 9 | "runArgs": ["--network=host"], 10 | 11 | "customizations": { 12 | "vscode": { 13 | "settings": { 14 | "terminal.integrated.shell.linux": "/bin/bash" 15 | }, 16 | "extensions": [ 17 | "ms-kubernetes-tools.vscode-kubernetes-tools", 18 | "ms-azuretools.vscode-docker" 19 | ] 20 | } 21 | }, 22 | 23 | "onCreateCommand": "bash .devcontainer/post-install.sh" 24 | } 25 | 26 | -------------------------------------------------------------------------------- /.devcontainer/post-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 5 | chmod +x ./kind 6 | mv ./kind /usr/local/bin/kind 7 | 8 | curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/linux/amd64 9 | chmod +x kubebuilder 10 | mv kubebuilder /usr/local/bin/ 11 | 12 | KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt) 13 | curl -LO "https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/amd64/kubectl" 14 | chmod +x kubectl 15 | mv kubectl /usr/local/bin/kubectl 16 | 17 | docker network create -d=bridge --subnet=172.19.0.0/24 kind 18 | 19 | kind version 20 | kubebuilder version 21 | docker --version 22 | go version 23 | kubectl version --client 24 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug encountered while using the Falco Kubernetes Operator 4 | labels: kind/bug 5 | 6 | --- 7 | 8 | 12 | 13 | **What happened**: 14 | 15 | **What you expected to happen**: 16 | 17 | **How to reproduce it (as minimally and precisely as possible)**: 18 | 19 | **Anything else we need to know?**: 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement Request 3 | about: Suggest an enhancement to the Falco Kubernetes Operator 4 | labels: kind/feature 5 | 6 | --- 7 | 8 | 9 | **What would you like to be added**: 10 | 11 | **Why is this needed**: 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/failing-tests.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Failing Test 3 | about: Report test failures in CI jobs 4 | labels: kind/failing-test 5 | 6 | --- 7 | 8 | 9 | 10 | **Which jobs are failing**: 11 | 12 | **Which test(s) are failing**: 13 | 14 | **Since when has it been failing**: 15 | 16 | **Test link**: 17 | 18 | **Reason for failure**: 19 | 20 | **Anything else we need to know**: 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | **What type of PR is this?** 10 | 11 | > Uncomment one (or more) `/kind <>` lines: 12 | 13 | > /kind bug 14 | 15 | > /kind cleanup 16 | 17 | > /kind design 18 | 19 | > /kind documentation 20 | 21 | > /kind failing-test 22 | 23 | > /kind feature 24 | 25 | **Any specific area of the project related to this PR?** 26 | 27 | > Uncomment one (or more) `/area <>` lines: 28 | 29 | > /area falco-operator 30 | 31 | > /area artifact-operator 32 | 33 | > /area pkg 34 | 35 | > /area api 36 | 37 | > /area docs 38 | 39 | **What this PR does / why we need it**: 40 | 41 | **Which issue(s) this PR fixes**: 42 | 43 | 48 | 49 | Fixes # 50 | 51 | **Special notes for your reviewer**: 52 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 10 8 | groups: 9 | gomod: 10 | update-types: 11 | - "patch" 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | open-pull-requests-limit: 10 18 | groups: 19 | actions: 20 | update-types: 21 | - "minor" 22 | - "patch" 23 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | schedule: 11 | - cron: '28 11 * * 2' 12 | 13 | jobs: 14 | analyze: 15 | name: Analyze 16 | runs-on: ubuntu-latest 17 | permissions: 18 | actions: read 19 | contents: read 20 | security-events: write 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | language: 26 | - go 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@d23060145bc9131d50558d5d4185494a20208101 # v2.2.8 32 | with: 33 | languages: ${{ matrix.language }} 34 | - name: Autobuild 35 | uses: github/codeql-action/autobuild@d23060145bc9131d50558d5d4185494a20208101 # v2.2.8 36 | - name: Perform CodeQL Analysis 37 | uses: github/codeql-action/analyze@d23060145bc9131d50558d5d4185494a20208101 # v2.2.8 38 | -------------------------------------------------------------------------------- /.github/workflows/docker-image-build.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | docker-configure: 10 | if: ${{ github.event_name == 'push' }} 11 | runs-on: ubuntu-22.04 12 | outputs: 13 | release: ${{ steps.vars.outputs.release }} 14 | commit: ${{ steps.vars.outputs.commit }} 15 | build_date: ${{ steps.vars.outputs.build_date }} 16 | steps: 17 | - name: Set version fields 18 | id: vars 19 | run: | 20 | echo "release=${{ github.sha }}" >> $GITHUB_OUTPUT 21 | echo "commit=${{ github.sha }}" >> $GITHUB_OUTPUT 22 | echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT 23 | 24 | falco-operator: 25 | if: ${{ github.event_name == 'push' }} 26 | needs: docker-configure 27 | uses: ./.github/workflows/docker-image.yml 28 | secrets: inherit 29 | with: 30 | release: ${{ needs.docker-configure.outputs.release }} 31 | commit: ${{ needs.docker-configure.outputs.commit }} 32 | build_date: ${{ needs.docker-configure.outputs.build_date }} 33 | image_name: ${{ vars.FALCO_OPERATOR_IMAGE_NAME }} 34 | component_name: "falco" 35 | 36 | artifact-operator: 37 | if: ${{ github.event_name == 'push' }} 38 | needs: docker-configure 39 | uses: ./.github/workflows/docker-image.yml 40 | secrets: inherit 41 | with: 42 | release: ${{ needs.docker-configure.outputs.release }} 43 | commit: ${{ needs.docker-configure.outputs.commit }} 44 | build_date: ${{ needs.docker-configure.outputs.build_date }} 45 | image_name: ${{ vars.ARTIFACT_OPERATOR_IMAGE_NAME }} 46 | component_name: "artifact" 47 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | component_name: 7 | description: "The name of the component being built" 8 | required: true 9 | type: string 10 | release: 11 | description: "The release version to use for tagging the image" 12 | required: true 13 | type: string 14 | commit: 15 | description: "The Git commit hash used for the build" 16 | required: true 17 | type: string 18 | build_date: 19 | description: "The date when the build is performed" 20 | required: true 21 | type: string 22 | image_name: 23 | description: "The Docker image name and path to push to registry" 24 | required: true 25 | type: string 26 | sign: 27 | description: "Whether to sign the Docker image with Cosign" 28 | required: false 29 | default: false 30 | type: boolean 31 | outputs: 32 | digest: 33 | description: The digest of the pushed image. 34 | value: ${{ jobs.docker-image.outputs.digest }} 35 | 36 | jobs: 37 | docker-image: 38 | runs-on: ubuntu-22.04 39 | outputs: 40 | image: ${{ steps.build-and-push.outputs.image }} 41 | digest: ${{ steps.build-and-push.outputs.digest }} 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 45 | with: 46 | fetch-depth: 0 47 | 48 | - name: Set up QEMU 49 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 50 | 51 | - name: Set up Docker Buildx 52 | id: Buildx 53 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 54 | 55 | - name: Login to Docker Hub 56 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 57 | with: 58 | username: ${{ secrets.DOCKERHUB_USER }} 59 | password: ${{ secrets.DOCKERHUB_SECRET }} 60 | 61 | - name: Docker Meta 62 | id: meta_image 63 | uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 64 | with: 65 | # list of Docker images to use as base name for tags 66 | images: | 67 | ${{ inputs.image_name }} 68 | tags: | 69 | type=ref,event=branch 70 | type=semver,pattern={{ version }} 71 | type=semver,pattern={{ major }} 72 | type=semver,pattern={{ major }}.{{ minor }} 73 | 74 | - name: Build and push 75 | id: build-and-push 76 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 77 | with: 78 | context: . 79 | platforms: linux/amd64,linux/arm64 80 | push: true 81 | tags: ${{ steps.meta_image.outputs.tags }} 82 | file: ./build/Dockerfile 83 | build-args: | 84 | RELEASE=${{ inputs.release }} 85 | COMMIT=${{ inputs.commit }} 86 | BUILD_DATE=${{ inputs.build_date }} 87 | COMPONENT=${{ inputs.component_name }} 88 | 89 | - name: Install Cosign 90 | if: ${{ inputs.sign }} 91 | uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2 92 | 93 | - name: Sign the images with GitHub OIDC Token 94 | if: ${{ inputs.sign }} 95 | env: 96 | DIGEST: ${{ steps.build-and-push.outputs.digest }} 97 | TAGS: ${{ steps.meta_image.outputs.tags }} 98 | COSIGN_YES: "true" 99 | run: echo "${TAGS}" | xargs -I {} cosign sign {}@${DIGEST} 100 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | lint: 11 | name: Lint golang files 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Clone the code 15 | uses: actions/checkout@v4.2.2 16 | 17 | - name: Setup Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version-file: go.mod 21 | 22 | - name: Run linter 23 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 24 | with: 25 | version: v2.1.6 26 | -------------------------------------------------------------------------------- /.github/workflows/test-e2e.yml: -------------------------------------------------------------------------------- 1 | name: E2E Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | test-e2e: 11 | name: tests-e2e 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Clone the code 15 | uses: actions/checkout@v4.2.2 16 | 17 | - name: Setup Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version-file: go.mod 21 | 22 | - name: Install the latest version of kind 23 | run: | 24 | curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 25 | chmod +x ./kind 26 | sudo mv ./kind /usr/local/bin/kind 27 | 28 | - name: Verify kind installation 29 | run: kind version 30 | 31 | - name: Create kind cluster 32 | run: kind create cluster 33 | 34 | - name: Running Test e2e 35 | run: | 36 | go mod tidy 37 | make test-e2e 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: Unit 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Clone the code 15 | uses: actions/checkout@v4.2.2 16 | 17 | - name: Setup Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version-file: go.mod 21 | 22 | - name: Running Tests 23 | run: | 24 | go mod tidy 25 | make test 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | Dockerfile.cross 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Go workspace file 17 | go.work 18 | 19 | # Kubernetes Generated files - skip generated files, except for vendored files 20 | !vendor/**/zz_generated.* 21 | 22 | # editor and IDE paraphernalia 23 | .idea 24 | .vscode 25 | *.swp 26 | *.swo 27 | *~ 28 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | allow-parallel-runners: true 4 | linters: 5 | default: none 6 | enable: 7 | - asciicheck 8 | - bodyclose 9 | - copyloopvar 10 | - dogsled 11 | - dupl 12 | - errcheck 13 | - errorlint 14 | - exhaustive 15 | - ginkgolinter 16 | - goconst 17 | - gocritic 18 | - gocyclo 19 | - godot 20 | - goheader 21 | - gomodguard 22 | - goprintffuncname 23 | - gosec 24 | - govet 25 | - ineffassign 26 | - lll 27 | - misspell 28 | - nakedret 29 | - noctx 30 | - nolintlint 31 | - prealloc 32 | - revive 33 | - rowserrcheck 34 | - staticcheck 35 | - unconvert 36 | - unparam 37 | - unused 38 | - whitespace 39 | settings: 40 | dupl: 41 | threshold: 300 42 | exhaustive: 43 | default-signifies-exhaustive: true 44 | goconst: 45 | min-len: 2 46 | min-occurrences: 2 47 | gocritic: 48 | enabled-tags: 49 | - diagnostic 50 | - experimental 51 | - opinionated 52 | - performance 53 | - style 54 | goheader: 55 | values: 56 | const: 57 | AUTHORS: The Falco Authors 58 | template: |- 59 | Copyright (C) {{ YEAR }} {{ AUTHORS }} 60 | 61 | Licensed under the Apache License, Version 2.0 (the "License"); 62 | you may not use this file except in compliance with the License. 63 | You may obtain a copy of the License at 64 | 65 | http://www.apache.org/licenses/LICENSE-2.0 66 | 67 | Unless required by applicable law or agreed to in writing, software 68 | distributed under the License is distributed on an "AS IS" BASIS, 69 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 70 | See the License for the specific language governing permissions and 71 | limitations under the License. 72 | 73 | SPDX-License-Identifier: Apache-2.0 74 | lll: 75 | line-length: 150 76 | misspell: 77 | locale: US 78 | nolintlint: 79 | require-explanation: true 80 | require-specific: true 81 | allow-unused: false 82 | revive: 83 | rules: 84 | - name: optimize-operands-order 85 | - name: identical-branches 86 | - name: comment-spacings 87 | - name: package-comments 88 | - name: range 89 | - name: range-val-address 90 | - name: receiver-naming 91 | - name: range-val-in-closure 92 | - name: superfluous-else 93 | - name: unhandled-error 94 | - name: exported 95 | arguments: 96 | - disableStutteringCheck 97 | exclusions: 98 | generated: lax 99 | rules: 100 | - linters: 101 | - lll 102 | path: api/* 103 | - linters: 104 | - dupl 105 | - lll 106 | path: internal/* 107 | - linters: 108 | - govet 109 | text: declaration of "(err|ctx)" shadows declaration at 110 | - linters: 111 | - errorlint 112 | text: type switch on error will fail on wrapped errors. Use errors.As to check for specific errors 113 | - linters: 114 | - gosec 115 | path: _test\.go 116 | - linters: 117 | - gocyclo 118 | path: main.go 119 | paths: 120 | - third_party$ 121 | - builtin$ 122 | - examples$ 123 | formatters: 124 | enable: 125 | - gci 126 | - gofmt 127 | - goimports 128 | settings: 129 | gci: 130 | sections: 131 | - standard 132 | - default 133 | - prefix(github.com/falcosecurity/falco-operator) 134 | goimports: 135 | local-prefixes: 136 | - github.com/falcosecurity/falco-operator 137 | exclusions: 138 | generated: lax 139 | paths: 140 | - third_party$ 141 | - builtin$ 142 | - examples$ 143 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | project_name: falco-operator 4 | before: 5 | hooks: 6 | - go mod tidy 7 | 8 | builds: 9 | - id: "falco-operator" 10 | binary: "falco-operator" 11 | goos: 12 | - linux 13 | goarch: 14 | - amd64 15 | - arm64 16 | 17 | ldflags: | 18 | -X github.com/falcosecurity/falco-operator/internal/pkg/version.BuildDate={{ .Date }} 19 | -X github.com/falcosecurity/falco-operator/internal/pkg/version.GitCommit={{ .Commit }} 20 | -X github.com/falcosecurity/falco-operator/internal/pkg/version.SemVersion={{ if .IsSnapshot }}{{ .Commit }}{{ else }}{{ .Version }}{{ end }} 21 | -s 22 | -w 23 | main: ./cmd/falco/main.go 24 | env: 25 | - GO111MODULE=on 26 | - CGO_ENABLED=0 27 | - id: "artifact-operator" 28 | binary: "artifact-operator" 29 | goos: 30 | - linux 31 | goarch: 32 | - amd64 33 | - arm64 34 | 35 | ldflags: | 36 | -X github.com/falcosecurity/falco-operator/internal/pkg/version.BuildDate={{ .Date }} 37 | -X github.com/falcosecurity/falco-operator/internal/pkg/version.GitCommit={{ .Commit }} 38 | -X github.com/falcosecurity/falco-operator/internal/pkg/version.SemVersion={{ if .IsSnapshot }}{{ .Commit }}{{ else }}{{ .Version }}{{ end }} 39 | -s 40 | -w 41 | main: ./cmd/artifact/main.go 42 | env: 43 | - GO111MODULE=on 44 | - CGO_ENABLED=0 45 | 46 | snapshot: 47 | version_template: "{{ .ShortCommit }}" 48 | 49 | release: 50 | prerelease: auto 51 | mode: replace 52 | 53 | changelog: 54 | use: github-native 55 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - alacuku 3 | - leogr 4 | - FedeDP 5 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: falcosecurity.dev 6 | layout: 7 | - go.kubebuilder.io/v4 8 | multigroup: true 9 | projectName: falco-operator 10 | repo: github.com/falcosecurity/falco-operator 11 | resources: 12 | - api: 13 | crdVersion: v1 14 | namespaced: true 15 | controller: true 16 | domain: falcosecurity.dev 17 | group: instance 18 | kind: Falco 19 | path: github.com/falcosecurity/falco-operator/api/v1alpha1 20 | plural: falcos 21 | version: v1alpha1 22 | - api: 23 | crdVersion: v1 24 | namespaced: true 25 | controller: true 26 | domain: falcosecurity.dev 27 | group: artifact 28 | kind: Config 29 | path: github.com/falcosecurity/falco-operator/api/artifact/v1alpha1 30 | version: v1alpha1 31 | - api: 32 | crdVersion: v1 33 | namespaced: true 34 | controller: true 35 | domain: falcosecurity.dev 36 | group: artifact 37 | kind: Rulesfile 38 | path: github.com/falcosecurity/falco-operator/api/artifact/v1alpha1 39 | version: v1alpha1 40 | - api: 41 | crdVersion: v1 42 | namespaced: true 43 | controller: true 44 | domain: falcosecurity.dev 45 | group: artifact 46 | kind: Plugin 47 | path: github.com/falcosecurity/falco-operator/api/artifact/v1alpha1 48 | plural: plugins 49 | version: v1alpha1 50 | version: "3" 51 | -------------------------------------------------------------------------------- /api/artifact/v1alpha1/config_types.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // ConfigSpec defines the desired state of Config. 24 | type ConfigSpec struct { 25 | // Config is the configuration for Falco deployment. 26 | // +kubebuilder:validation:Required 27 | Config string `json:"config,omitempty"` 28 | // Priority specifies the priority of the rulesfile.\ 29 | // The higher the value, the higher the priority. 30 | // +kubebuilder:validation:Minimum=0 31 | // +kubebuilder:validation:Maximum=99 32 | // +kubebuilder:default=50 33 | Priority int32 `json:"priority,omitempty"` 34 | // Selector is used to select the nodes where the config should be applied. 35 | Selector *metav1.LabelSelector `json:"selector,omitempty"` 36 | } 37 | 38 | // ConfigStatus defines the observed state of Config. 39 | type ConfigStatus struct { 40 | // The current status of the config resource. 41 | Conditions []metav1.Condition `json:"conditions,omitempty"` 42 | } 43 | 44 | // +kubebuilder:object:root=true 45 | // +kubebuilder:subresource:status 46 | 47 | // Config is the Schema for the configs API. 48 | type Config struct { 49 | metav1.TypeMeta `json:",inline"` 50 | metav1.ObjectMeta `json:"metadata,omitempty"` 51 | 52 | Spec ConfigSpec `json:"spec,omitempty"` 53 | Status ConfigStatus `json:"status,omitempty"` 54 | } 55 | 56 | // +kubebuilder:object:root=true 57 | 58 | // ConfigList contains a list of Config. 59 | type ConfigList struct { 60 | metav1.TypeMeta `json:",inline"` 61 | metav1.ListMeta `json:"metadata,omitempty"` 62 | Items []Config `json:"items"` 63 | } 64 | 65 | func init() { 66 | SchemeBuilder.Register(&Config{}, &ConfigList{}) 67 | } 68 | -------------------------------------------------------------------------------- /api/artifact/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package v1alpha1 contains API Schema definitions for the artifact v1alpha1 API group. 18 | // +kubebuilder:object:generate=true 19 | // +groupName=artifact.falcosecurity.dev 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects. 29 | GroupVersion = schema.GroupVersion{Group: "artifact.falcosecurity.dev", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/artifact/v1alpha1/plugin_types.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controller defines controllers' logic. 18 | 19 | package v1alpha1 20 | 21 | import ( 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | 24 | commonv1alpha1 "github.com/falcosecurity/falco-operator/api/common/v1alpha1" 25 | ) 26 | 27 | // PluginSpec defines the desired state of Plugin. 28 | type PluginSpec struct { 29 | // OCIArtifact specifies the reference to an OCI artifact. 30 | OCIArtifact *commonv1alpha1.OCIArtifact `json:"ociArtifact,omitempty"` 31 | // Config specifies the configuration for the plugin. 32 | Config *PluginConfig `json:"config,omitempty"` 33 | // Selector is used to select the nodes where the plugin should be applied. 34 | Selector *metav1.LabelSelector `json:"selector,omitempty"` 35 | } 36 | 37 | // PluginConfig defines the configuration for the plugin. 38 | type PluginConfig struct { 39 | // Name is the name of the plugin. 40 | // If omitted, the name of the CRD will be used. 41 | Name string `json:"name,omitempty"` 42 | // LibraryPath is the path to the plugin library, e.g., /usr/share/falco/plugins/myplugin.so. 43 | // If omitted, it is set to /usr/share/falco/plugins/plugin-name.so. 44 | LibraryPath string `json:"libraryPath,omitempty"` 45 | // InitConfig is the initialization configuration for the plugin. 46 | InitConfig map[string]string `json:"initConfig,omitempty"` 47 | // OpenParams is the open parameters for the plugin. 48 | OpenParams string `json:"openParams,omitempty"` 49 | } 50 | 51 | // PluginStatus defines the observed state of Plugin. 52 | type PluginStatus struct { 53 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 54 | // Important: Run "make" to regenerate code after modifying this file 55 | } 56 | 57 | // +kubebuilder:object:root=true 58 | // +kubebuilder:subresource:status 59 | // +kubebuilder:resource:path=plugins 60 | 61 | // Plugin is the Schema for the plugin API. 62 | type Plugin struct { 63 | metav1.TypeMeta `json:",inline"` 64 | metav1.ObjectMeta `json:"metadata,omitempty"` 65 | 66 | Spec PluginSpec `json:"spec,omitempty"` 67 | Status PluginStatus `json:"status,omitempty"` 68 | } 69 | 70 | // +kubebuilder:object:root=true 71 | 72 | // PluginList contains a list of Plugin. 73 | type PluginList struct { 74 | metav1.TypeMeta `json:",inline"` 75 | metav1.ListMeta `json:"metadata,omitempty"` 76 | Items []Plugin `json:"items"` 77 | } 78 | 79 | func init() { 80 | SchemeBuilder.Register(&Plugin{}, &PluginList{}) 81 | } 82 | -------------------------------------------------------------------------------- /api/artifact/v1alpha1/rulesfile_types.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controller defines controllers' logic. 18 | 19 | package v1alpha1 20 | 21 | import ( 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | 24 | commonv1alpha1 "github.com/falcosecurity/falco-operator/api/common/v1alpha1" 25 | ) 26 | 27 | // RulesfileSpec defines the desired state of Rulesfile. 28 | type RulesfileSpec struct { 29 | // OCIArtifact specifies the reference to an OCI artifact. 30 | OCIArtifact *commonv1alpha1.OCIArtifact `json:"ociArtifact,omitempty"` 31 | // InlineRules specifies the rules as a string. 32 | InlineRules *string `json:"inlineRules,omitempty"` 33 | // Priority specifies the priority of the rulesfile.\ 34 | // The higher the value, the higher the priority. 35 | // +kubebuilder:validation:Minimum=0 36 | // +kubebuilder:validation:Maximum=99 37 | // +kubebuilder:default=50 38 | Priority int32 `json:"priority,omitempty"` 39 | // Selector is used to select the nodes where the rulesfile should be applied. 40 | Selector *metav1.LabelSelector `json:"selector,omitempty"` 41 | } 42 | 43 | // RulesfileStatus defines the observed state of Rulesfile. 44 | type RulesfileStatus struct { 45 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 46 | // Important: Run "make" to regenerate code after modifying this file 47 | } 48 | 49 | // +kubebuilder:object:root=true 50 | // +kubebuilder:subresource:status 51 | 52 | // Rulesfile is the Schema for the rulesfiles API. 53 | type Rulesfile struct { 54 | metav1.TypeMeta `json:",inline"` 55 | metav1.ObjectMeta `json:"metadata,omitempty"` 56 | 57 | Spec RulesfileSpec `json:"spec,omitempty"` 58 | Status RulesfileStatus `json:"status,omitempty"` 59 | } 60 | 61 | // +kubebuilder:object:root=true 62 | 63 | // RulesfileList contains a list of Rulesfile. 64 | type RulesfileList struct { 65 | metav1.TypeMeta `json:",inline"` 66 | metav1.ListMeta `json:"metadata,omitempty"` 67 | Items []Rulesfile `json:"items"` 68 | } 69 | 70 | func init() { 71 | SchemeBuilder.Register(&Rulesfile{}, &RulesfileList{}) 72 | } 73 | -------------------------------------------------------------------------------- /api/common/v1alpha1/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package v1alpha1 contains common types used across apis. 18 | package v1alpha1 19 | 20 | // ConditionType represents a Falco condition type. 21 | // +kubebuilder:validation:MinLength=1 22 | type ConditionType string 23 | 24 | const ( 25 | // ConditionAvailable indicates whether enough pods are ready to provide the 26 | // service. 27 | // The possible status values for this condition type are: 28 | // - True: all pods are running and ready, the service is fully available. 29 | // - Degraded: some pods aren't ready, the service is partially available. 30 | // - False: no pods are running, the service is totally unavailable. 31 | // - Unknown: the operator couldn't determine the condition status. 32 | ConditionAvailable ConditionType = "ConditionAvailable" 33 | // ConditionReconciled indicates whether the operator has reconciled the state of 34 | // the underlying resources with the object's spec. 35 | // The possible status values for this condition type are: 36 | // - True: the reconciliation was successful. 37 | // - False: the reconciliation failed. 38 | // - Unknown: the operator couldn't determine the condition status. 39 | ConditionReconciled ConditionType = "ConditionReconciled" 40 | ) 41 | 42 | // OCIArtifact defines the structure for specifying an OCI artifact reference. 43 | // +kubebuilder:object:generate=true 44 | type OCIArtifact struct { 45 | // Reference is the OCI artifact reference. 46 | // +kubebuilder:validation:Required 47 | Reference string `json:"reference,omitempty"` 48 | 49 | // PullSecret contains authentication details used to pull the OCI artifact. 50 | PullSecret *OCIPullSecret `json:"pullSecret,omitempty"` 51 | } 52 | 53 | // OCIPullSecret defines the structure for specifying authentication details for an OCI artifact. 54 | // +kubebuilder:object:generate=true 55 | type OCIPullSecret struct { 56 | // SecretName is the name of the secret containing credentials. 57 | // +kubebuilder:validation:Required 58 | SecretName string `json:"secretName,omitempty"` 59 | 60 | // UsernameKey is the key in the secret that contains the username. 61 | // +kubebuilder:default=username 62 | UsernameKey string `json:"usernameKey,omitempty"` 63 | 64 | // PasswordKey is the key in the secret that contains the password. 65 | // +kubebuilder:default=password 66 | PasswordKey string `json:"passwordKey,omitempty"` 67 | } 68 | -------------------------------------------------------------------------------- /api/common/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | // Copyright (C) 2025 The Falco Authors 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | // SPDX-License-Identifier: Apache-2.0 18 | 19 | // Package controller defines controllers' logic. 20 | 21 | // Code generated by controller-gen. DO NOT EDIT. 22 | 23 | package v1alpha1 24 | 25 | import () 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in *OCIArtifact) DeepCopyInto(out *OCIArtifact) { 29 | *out = *in 30 | if in.PullSecret != nil { 31 | in, out := &in.PullSecret, &out.PullSecret 32 | *out = new(OCIPullSecret) 33 | **out = **in 34 | } 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIArtifact. 38 | func (in *OCIArtifact) DeepCopy() *OCIArtifact { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(OCIArtifact) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 48 | func (in *OCIPullSecret) DeepCopyInto(out *OCIPullSecret) { 49 | *out = *in 50 | } 51 | 52 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIPullSecret. 53 | func (in *OCIPullSecret) DeepCopy() *OCIPullSecret { 54 | if in == nil { 55 | return nil 56 | } 57 | out := new(OCIPullSecret) 58 | in.DeepCopyInto(out) 59 | return out 60 | } 61 | -------------------------------------------------------------------------------- /api/instance/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package v1alpha1 contains API Schema definitions for the instance v1alpha1 API group. 18 | // +kubebuilder:object:generate=true 19 | // +groupName=instance.falcosecurity.dev 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects. 29 | GroupVersion = schema.GroupVersion{Group: "instance.falcosecurity.dev", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/instance/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | // Copyright (C) 2025 The Falco Authors 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | // SPDX-License-Identifier: Apache-2.0 18 | 19 | // Package controller defines controllers' logic. 20 | 21 | // Code generated by controller-gen. DO NOT EDIT. 22 | 23 | package v1alpha1 24 | 25 | import ( 26 | "k8s.io/api/core/v1" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | runtime "k8s.io/apimachinery/pkg/runtime" 29 | ) 30 | 31 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 32 | func (in *Falco) DeepCopyInto(out *Falco) { 33 | *out = *in 34 | out.TypeMeta = in.TypeMeta 35 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 36 | in.Spec.DeepCopyInto(&out.Spec) 37 | in.Status.DeepCopyInto(&out.Status) 38 | } 39 | 40 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Falco. 41 | func (in *Falco) DeepCopy() *Falco { 42 | if in == nil { 43 | return nil 44 | } 45 | out := new(Falco) 46 | in.DeepCopyInto(out) 47 | return out 48 | } 49 | 50 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 51 | func (in *Falco) DeepCopyObject() runtime.Object { 52 | if c := in.DeepCopy(); c != nil { 53 | return c 54 | } 55 | return nil 56 | } 57 | 58 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 59 | func (in *FalcoList) DeepCopyInto(out *FalcoList) { 60 | *out = *in 61 | out.TypeMeta = in.TypeMeta 62 | in.ListMeta.DeepCopyInto(&out.ListMeta) 63 | if in.Items != nil { 64 | in, out := &in.Items, &out.Items 65 | *out = make([]Falco, len(*in)) 66 | for i := range *in { 67 | (*in)[i].DeepCopyInto(&(*out)[i]) 68 | } 69 | } 70 | } 71 | 72 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FalcoList. 73 | func (in *FalcoList) DeepCopy() *FalcoList { 74 | if in == nil { 75 | return nil 76 | } 77 | out := new(FalcoList) 78 | in.DeepCopyInto(out) 79 | return out 80 | } 81 | 82 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 83 | func (in *FalcoList) DeepCopyObject() runtime.Object { 84 | if c := in.DeepCopy(); c != nil { 85 | return c 86 | } 87 | return nil 88 | } 89 | 90 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 91 | func (in *FalcoSpec) DeepCopyInto(out *FalcoSpec) { 92 | *out = *in 93 | if in.Replicas != nil { 94 | in, out := &in.Replicas, &out.Replicas 95 | *out = new(int32) 96 | **out = **in 97 | } 98 | if in.PodTemplateSpec != nil { 99 | in, out := &in.PodTemplateSpec, &out.PodTemplateSpec 100 | *out = new(v1.PodTemplateSpec) 101 | (*in).DeepCopyInto(*out) 102 | } 103 | } 104 | 105 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FalcoSpec. 106 | func (in *FalcoSpec) DeepCopy() *FalcoSpec { 107 | if in == nil { 108 | return nil 109 | } 110 | out := new(FalcoSpec) 111 | in.DeepCopyInto(out) 112 | return out 113 | } 114 | 115 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 116 | func (in *FalcoStatus) DeepCopyInto(out *FalcoStatus) { 117 | *out = *in 118 | if in.Conditions != nil { 119 | in, out := &in.Conditions, &out.Conditions 120 | *out = make([]metav1.Condition, len(*in)) 121 | for i := range *in { 122 | (*in)[i].DeepCopyInto(&(*out)[i]) 123 | } 124 | } 125 | } 126 | 127 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FalcoStatus. 128 | func (in *FalcoStatus) DeepCopy() *FalcoStatus { 129 | if in == nil { 130 | return nil 131 | } 132 | out := new(FalcoStatus) 133 | in.DeepCopyInto(out) 134 | return out 135 | } 136 | -------------------------------------------------------------------------------- /build/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cgr.dev/chainguard/go AS builder 2 | WORKDIR /tmp/builder 3 | 4 | ARG RELEASE 5 | ARG COMMIT 6 | ARG BUILD_DATE 7 | ARG PROJECT=github.com/falcosecurity/falco-operator 8 | ARG COMPONENT 9 | 10 | RUN test -n "$RELEASE" || ( echo "The RELEASE argument is unset. Aborting" && false ) 11 | RUN test -n "$COMMIT" || ( echo "The COMMIT argument is unset. Aborting" && false ) 12 | RUN test -n "$BUILD_DATE" || ( echo "The BUILD_DATE argument is unset. Aborting" && false ) 13 | RUN test -n "$COMPONENT" || ( echo "The COMPONENT argument is unset. Aborting" && false ) 14 | 15 | COPY go.mod ./go.mod 16 | COPY go.sum ./go.sum 17 | RUN go mod download 18 | 19 | # Copy the go source 20 | COPY cmd/${COMPONENT}/main.go cmd/${COMPONENT}/main.go 21 | COPY api/ api/ 22 | COPY internal/ internal/ 23 | COPY controllers/ controllers/ 24 | 25 | RUN CGO_ENABLED=0 \ 26 | GOOS=$(go env GOOS) \ 27 | GOARCH=$(go env GOARCH) \ 28 | go build -ldflags \ 29 | "-s \ 30 | -w \ 31 | -X '${PROJECT}/internal/pkg/version.SemVersion=${RELEASE}' \ 32 | -X '${PROJECT}/internal/pkg/version.GitCommit=${COMMIT}' \ 33 | -X '${PROJECT}/internal/pkg/version.BuildDate=${BUILD_DATE}'" \ 34 | -o ${COMPONENT} ./cmd/${COMPONENT}/main.go 35 | 36 | RUN echo ${RELEASE} 37 | 38 | FROM cgr.dev/chainguard/static:latest 39 | 40 | ARG COMPONENT 41 | 42 | COPY --from=builder /tmp/builder/${COMPONENT} /usr/bin/manager 43 | USER 65532:65532 44 | 45 | ENTRYPOINT [ "/usr/bin/manager" ] 46 | -------------------------------------------------------------------------------- /cmd/artifact/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package main is the entrypoint for the artifact-operator binary. 18 | package main 19 | -------------------------------------------------------------------------------- /cmd/falco/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package main is the entrypoint for the falco-operator binary. 18 | package main 19 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/instance.falcosecurity.dev_falcos.yaml 6 | - bases/artifact.falcosecurity.dev_configs.yaml 7 | - bases/artifact.falcosecurity.dev_rulesfiles.yaml 8 | - bases/artifact.falcosecurity.dev_plugins.yaml 9 | # +kubebuilder:scaffold:crdkustomizeresource 10 | 11 | patches: 12 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 13 | # patches here are for enabling the conversion webhook for each CRD 14 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 15 | 16 | # [WEBHOOK] To enable webhook, uncomment the following section 17 | # the following config is for teaching kustomize how to do kustomization for CRDs. 18 | #configurations: 19 | #- kustomizeconfig.yaml 20 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | 7 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: "falco-operator" 6 | app.kubernetes.io/part-of: "falco" 7 | name: falco-operator 8 | --- 9 | apiVersion: apps/v1 10 | kind: Deployment 11 | metadata: 12 | name: falco-operator 13 | namespace: falco-operator 14 | labels: 15 | app.kubernetes.io/name: "falco-operator" 16 | app.kubernetes.io/instance: "falco-operator" 17 | app.kubernetes.io/part-of: "falco" 18 | spec: 19 | selector: 20 | matchLabels: 21 | control-plane: falco-operator 22 | app.kubernetes.io/name: falco-operator 23 | replicas: 1 24 | template: 25 | metadata: 26 | annotations: 27 | kubectl.kubernetes.io/default-container: manager 28 | labels: 29 | control-plane: falco-operator 30 | app.kubernetes.io/name: falco-operator 31 | spec: 32 | securityContext: 33 | runAsNonRoot: true 34 | seccompProfile: 35 | type: RuntimeDefault 36 | containers: 37 | - command: 38 | - /usr/bin/manager 39 | args: 40 | - --health-probe-bind-address=:8081 41 | image: falcosecurity/falco-operator:latest 42 | imagePullPolicy: IfNotPresent 43 | name: falco-operator 44 | ports: [] 45 | securityContext: 46 | allowPrivilegeEscalation: false 47 | capabilities: 48 | drop: 49 | - "ALL" 50 | livenessProbe: 51 | httpGet: 52 | path: /healthz 53 | port: 8081 54 | initialDelaySeconds: 15 55 | periodSeconds: 20 56 | readinessProbe: 57 | httpGet: 58 | path: /readyz 59 | port: 8081 60 | initialDelaySeconds: 5 61 | periodSeconds: 10 62 | resources: 63 | limits: 64 | cpu: 500m 65 | memory: 128Mi 66 | requests: 67 | cpu: 10m 68 | memory: 64Mi 69 | volumeMounts: [] 70 | volumes: [] 71 | serviceAccountName: falco-operator 72 | terminationGracePeriodSeconds: 10 73 | --- 74 | apiVersion: rbac.authorization.k8s.io/v1 75 | kind: ClusterRole 76 | metadata: 77 | name: falco-operator-role 78 | labels: 79 | app.kubernetes.io/name: "falco-operator" 80 | app.kubernetes.io/instance: "falco-operator" 81 | app.kubernetes.io/part-of: "falco" 82 | rules: 83 | - nonResourceURLs: 84 | - /metrics 85 | verbs: 86 | - get 87 | - apiGroups: 88 | - instance.falcosecurity.dev 89 | - artifact.falcosecurity.dev 90 | resources: 91 | - falcos 92 | - falcos/status 93 | - rulesfiles 94 | - rulesfiles/status 95 | - configs 96 | - configs/status 97 | - plugins 98 | - plugins/status 99 | verbs: 100 | - create 101 | - delete 102 | - get 103 | - list 104 | - patch 105 | - update 106 | - watch 107 | - apiGroups: 108 | - "" 109 | resources: 110 | - nodes 111 | verbs: 112 | - get 113 | - list 114 | - watch 115 | - apiGroups: 116 | - "rbac.authorization.k8s.io" 117 | resources: 118 | - clusterroles 119 | - clusterrolebindings 120 | verbs: 121 | - create 122 | - get 123 | - list 124 | - watch 125 | - apiGroups: 126 | - "" 127 | resources: 128 | - pods 129 | - services 130 | - configmaps 131 | - secrets 132 | - serviceaccounts 133 | verbs: 134 | - create 135 | - delete 136 | - get 137 | - list 138 | - patch 139 | - update 140 | - watch 141 | - apiGroups: 142 | - apps 143 | resources: 144 | - deployments 145 | - daemonsets 146 | verbs: 147 | - create 148 | - delete 149 | - get 150 | - list 151 | - patch 152 | - update 153 | - watch 154 | - apiGroups: 155 | - rbac.authorization.k8s.io 156 | resources: 157 | - roles 158 | - rolebindings 159 | verbs: 160 | - create 161 | - delete 162 | - get 163 | - list 164 | - patch 165 | - update 166 | - watch 167 | --- 168 | apiVersion: rbac.authorization.k8s.io/v1 169 | kind: ClusterRoleBinding 170 | metadata: 171 | name: falco-operator-rolebinding 172 | labels: 173 | app.kubernetes.io/name: "falco-operator" 174 | app.kubernetes.io/instance: "falco-operator" 175 | app.kubernetes.io/part-of: "falco" 176 | roleRef: 177 | apiGroup: rbac.authorization.k8s.io 178 | kind: ClusterRole 179 | name: falco-operator-role 180 | subjects: 181 | - kind: ServiceAccount 182 | name: falco-operator 183 | namespace: falco-operator 184 | --- 185 | apiVersion: v1 186 | kind: ServiceAccount 187 | metadata: 188 | name: falco-operator 189 | namespace: falco-operator 190 | labels: 191 | app.kubernetes.io/name: "falco-operator" 192 | app.kubernetes.io/instance: "falco-operator" 193 | app.kubernetes.io/part-of: "falco" 194 | 195 | -------------------------------------------------------------------------------- /config/samples/artifact_v1alpha1_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: artifact.falcosecurity.dev/v1alpha1 2 | kind: Config 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: falco-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: config-sample 8 | spec: 9 | config: |- 10 | engine: 11 | ebpf: 12 | buf_size_preset: 4 13 | drop_failed_exit: false 14 | probe: ${HOME}/.falco/falco-bpf.o 15 | kind: modern_ebpf 16 | kmod: 17 | buf_size_preset: 4 18 | drop_failed_exit: false 19 | modern_ebpf: 20 | buf_size_preset: 4 21 | cpus_for_each_buffer: 2 22 | drop_failed_exit: false 23 | falco_libs: 24 | thread_table_size: 262144 25 | file_output: 26 | enabled: false 27 | filename: ./events.txt 28 | keep_alive: false 29 | grpc: 30 | bind_address: unix:///run/falco/falco.sock 31 | enabled: false 32 | threadiness: 1 33 | -------------------------------------------------------------------------------- /config/samples/artifact_v1alpha1_plugin.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: artifact.falcosecurity.dev/v1alpha1 2 | kind: Plugin 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: falco-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: plugin-sample 8 | spec: 9 | ociArtifact: 10 | reference: ghcr.io/falcosecurity/plugins/plugin/container:main 11 | -------------------------------------------------------------------------------- /config/samples/artifact_v1alpha1_rulesfile.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: artifact.falcosecurity.dev/v1alpha1 2 | kind: Rulesfile 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: falco-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: rulesfile-sample 8 | spec: 9 | ociArtifact: 10 | reference: ghcr.io/falcosecurity/falco-rules:latest 11 | pullSecret: 12 | secretName: my-pull-secret -------------------------------------------------------------------------------- /config/samples/instance_v1alpha1_falco.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: instance.falcosecurity.dev/v1alpha1 2 | kind: Falco 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: falco-operator 6 | app.kubernetes.io/managed-by: kustomize 7 | name: falco-sample 8 | spec: 9 | # TODO(user): Add fields here 10 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - instance_v1alpha1_falco.yaml 4 | - artifact_v1alpha1_config.yaml 5 | - artifact_v1alpha1_rulesfile.yaml 6 | - artifact_v1alpha1_plugin.yaml 7 | # +kubebuilder:scaffold:manifestskustomizesamples 8 | -------------------------------------------------------------------------------- /controllers/artifact/config/controller_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controller defines controllers' logic. 18 | 19 | package config 20 | 21 | import ( 22 | "context" 23 | 24 | . "github.com/onsi/ginkgo/v2" 25 | . "github.com/onsi/gomega" 26 | "k8s.io/apimachinery/pkg/api/errors" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "k8s.io/apimachinery/pkg/types" 29 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 30 | 31 | artifactv1alpha1 "github.com/falcosecurity/falco-operator/api/artifact/v1alpha1" 32 | ) 33 | 34 | var _ = Describe("Config Controller", func() { 35 | Context("When reconciling a resource", func() { 36 | const resourceName = "test-resource" 37 | 38 | ctx := context.Background() 39 | 40 | typeNamespacedName := types.NamespacedName{ 41 | Name: resourceName, 42 | Namespace: "default", // TODO(user):Modify as needed 43 | } 44 | config := &artifactv1alpha1.Config{} 45 | 46 | BeforeEach(func() { 47 | By("creating the custom resource for the Kind Config") 48 | err := k8sClient.Get(ctx, typeNamespacedName, config) 49 | if err != nil && errors.IsNotFound(err) { 50 | resource := &artifactv1alpha1.Config{ 51 | ObjectMeta: metav1.ObjectMeta{ 52 | Name: resourceName, 53 | Namespace: "default", 54 | }, 55 | Spec: artifactv1alpha1.ConfigSpec{ 56 | Config: ` 57 | engine: 58 | ebpf: 59 | buf_size_preset: 4 60 | drop_failed_exit: false 61 | probe: ${HOME}/.falco/falco-bpf.o 62 | kind: modern_ebpf 63 | kmod: 64 | buf_size_preset: 4 65 | drop_failed_exit: false 66 | modern_ebpf: 67 | buf_size_preset: 4 68 | cpus_for_each_buffer: 2 69 | drop_failed_exit: false 70 | falco_libs: 71 | thread_table_size: 262144 72 | file_output: 73 | enabled: false 74 | filename: ./events.txt 75 | keep_alive: false 76 | grpc: 77 | bind_address: unix:///run/falco/falco.sock 78 | enabled: false 79 | threadiness: 1 80 | `, 81 | }, 82 | } 83 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 84 | } 85 | }) 86 | 87 | AfterEach(func() { 88 | // TODO(user): Cleanup logic after each test, like removing the resource instance. 89 | resource := &artifactv1alpha1.Config{} 90 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 91 | Expect(err).NotTo(HaveOccurred()) 92 | 93 | By("Cleanup the specific resource instance Config") 94 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 95 | }) 96 | It("should successfully reconcile the resource", func() { 97 | By("Reconciling the created resource") 98 | controllerReconciler := NewConfigReconciler(k8sClient, k8sClient.Scheme(), "test-node", "test-namespace") 99 | 100 | _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 101 | NamespacedName: typeNamespacedName, 102 | }) 103 | Expect(err).NotTo(HaveOccurred()) 104 | // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 105 | // Example: If you expect a certain status condition after reconciliation, verify it here. 106 | }) 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /controllers/artifact/config/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package config defines the config controller logic. 18 | // It handles the lifecycle of the Config resource. 19 | package config 20 | -------------------------------------------------------------------------------- /controllers/artifact/config/suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controller defines controllers' logic. 18 | 19 | package config 20 | 21 | import ( 22 | "context" 23 | "os" 24 | "path/filepath" 25 | "testing" 26 | 27 | . "github.com/onsi/ginkgo/v2" 28 | . "github.com/onsi/gomega" 29 | "k8s.io/client-go/kubernetes/scheme" 30 | "k8s.io/client-go/rest" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | "sigs.k8s.io/controller-runtime/pkg/envtest" 33 | logf "sigs.k8s.io/controller-runtime/pkg/log" 34 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 35 | 36 | artifactv1alpha1 "github.com/falcosecurity/falco-operator/api/artifact/v1alpha1" 37 | ) 38 | 39 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 40 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 41 | 42 | var ( 43 | ctx context.Context 44 | cancel context.CancelFunc 45 | testEnv *envtest.Environment 46 | cfg *rest.Config 47 | k8sClient client.Client 48 | ) 49 | 50 | func TestControllers(t *testing.T) { 51 | RegisterFailHandler(Fail) 52 | 53 | RunSpecs(t, "Controller Suite") 54 | } 55 | 56 | var _ = BeforeSuite(func() { 57 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 58 | 59 | ctx, cancel = context.WithCancel(context.TODO()) 60 | 61 | var err error 62 | err = artifactv1alpha1.AddToScheme(scheme.Scheme) 63 | Expect(err).NotTo(HaveOccurred()) 64 | 65 | // +kubebuilder:scaffold:scheme 66 | 67 | By("bootstrapping test environment") 68 | testEnv = &envtest.Environment{ 69 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, 70 | ErrorIfCRDPathMissing: true, 71 | } 72 | 73 | // Retrieve the first found binary directory to allow running tests from IDEs 74 | if getFirstFoundEnvTestBinaryDir() != "" { 75 | testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir() 76 | } 77 | 78 | // cfg is defined in this file globally. 79 | cfg, err = testEnv.Start() 80 | Expect(err).NotTo(HaveOccurred()) 81 | Expect(cfg).NotTo(BeNil()) 82 | 83 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 84 | Expect(err).NotTo(HaveOccurred()) 85 | Expect(k8sClient).NotTo(BeNil()) 86 | }) 87 | 88 | var _ = AfterSuite(func() { 89 | By("tearing down the test environment") 90 | cancel() 91 | err := testEnv.Stop() 92 | Expect(err).NotTo(HaveOccurred()) 93 | }) 94 | 95 | // getFirstFoundEnvTestBinaryDir locates the first binary in the specified path. 96 | // ENVTEST-based tests depend on specific binaries, usually located in paths set by 97 | // controller-runtime. When running tests directly (e.g., via an IDE) without using 98 | // Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured. 99 | // 100 | // This function streamlines the process by finding the required binaries, similar to 101 | // setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are 102 | // properly set up, run 'make setup-envtest' beforehand. 103 | func getFirstFoundEnvTestBinaryDir() string { 104 | basePath := filepath.Join("..", "..", "..", "bin", "k8s") 105 | entries, err := os.ReadDir(basePath) 106 | if err != nil { 107 | logf.Log.Error(err, "Failed to read directory", "path", basePath) 108 | return "" 109 | } 110 | for _, entry := range entries { 111 | if entry.IsDir() { 112 | return filepath.Join(basePath, entry.Name()) 113 | } 114 | } 115 | return "" 116 | } 117 | -------------------------------------------------------------------------------- /controllers/artifact/plugin/controller_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controller defines controllers' logic. 18 | 19 | package plugin 20 | 21 | import ( 22 | "context" 23 | 24 | . "github.com/onsi/ginkgo/v2" 25 | . "github.com/onsi/gomega" 26 | "k8s.io/apimachinery/pkg/api/errors" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "k8s.io/apimachinery/pkg/types" 29 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 30 | 31 | artifactv1alpha1 "github.com/falcosecurity/falco-operator/api/artifact/v1alpha1" 32 | ) 33 | 34 | var _ = Describe("Plugin Controller", func() { 35 | Context("When reconciling a resource", func() { 36 | const resourceName = "test-resource" 37 | 38 | ctx := context.Background() 39 | 40 | typeNamespacedName := types.NamespacedName{ 41 | Name: resourceName, 42 | Namespace: "default", // TODO(user):Modify as needed 43 | } 44 | plugin := &artifactv1alpha1.Plugin{} 45 | 46 | BeforeEach(func() { 47 | By("creating the custom resource for the Kind Plugin") 48 | err := k8sClient.Get(ctx, typeNamespacedName, plugin) 49 | if err != nil && errors.IsNotFound(err) { 50 | resource := &artifactv1alpha1.Plugin{ 51 | ObjectMeta: metav1.ObjectMeta{ 52 | Name: resourceName, 53 | Namespace: "default", 54 | }, 55 | // TODO(user): Specify other spec details if needed. 56 | } 57 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 58 | } 59 | }) 60 | 61 | AfterEach(func() { 62 | // TODO(user): Cleanup logic after each test, like removing the resource instance. 63 | resource := &artifactv1alpha1.Plugin{} 64 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 65 | Expect(err).NotTo(HaveOccurred()) 66 | 67 | By("Cleanup the specific resource instance Plugin") 68 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 69 | }) 70 | It("should successfully reconcile the resource", func() { 71 | By("Reconciling the created resource") 72 | controllerReconciler := NewPluginReconciler(k8sClient, k8sClient.Scheme(), "test-node", "test-namespace") 73 | 74 | _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 75 | NamespacedName: typeNamespacedName, 76 | }) 77 | Expect(err).NotTo(HaveOccurred()) 78 | // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 79 | // Example: If you expect a certain status condition after reconciliation, verify it here. 80 | }) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /controllers/artifact/plugin/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package plugin defines the plugin controller logic. 18 | // It handles the lifecycle of the Plugin resource. 19 | package plugin 20 | -------------------------------------------------------------------------------- /controllers/artifact/plugin/suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controller defines controllers' logic. 18 | 19 | package plugin 20 | 21 | import ( 22 | "context" 23 | "os" 24 | "path/filepath" 25 | "testing" 26 | 27 | . "github.com/onsi/ginkgo/v2" 28 | . "github.com/onsi/gomega" 29 | "k8s.io/client-go/kubernetes/scheme" 30 | "k8s.io/client-go/rest" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | "sigs.k8s.io/controller-runtime/pkg/envtest" 33 | logf "sigs.k8s.io/controller-runtime/pkg/log" 34 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 35 | 36 | artifactv1alpha1 "github.com/falcosecurity/falco-operator/api/artifact/v1alpha1" 37 | ) 38 | 39 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 40 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 41 | 42 | var ( 43 | ctx context.Context 44 | cancel context.CancelFunc 45 | testEnv *envtest.Environment 46 | cfg *rest.Config 47 | k8sClient client.Client 48 | ) 49 | 50 | func TestControllers(t *testing.T) { 51 | RegisterFailHandler(Fail) 52 | 53 | RunSpecs(t, "Controller Suite") 54 | } 55 | 56 | var _ = BeforeSuite(func() { 57 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 58 | 59 | ctx, cancel = context.WithCancel(context.TODO()) 60 | 61 | var err error 62 | err = artifactv1alpha1.AddToScheme(scheme.Scheme) 63 | Expect(err).NotTo(HaveOccurred()) 64 | 65 | // +kubebuilder:scaffold:scheme 66 | 67 | By("bootstrapping test environment") 68 | testEnv = &envtest.Environment{ 69 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, 70 | ErrorIfCRDPathMissing: true, 71 | } 72 | 73 | // Retrieve the first found binary directory to allow running tests from IDEs 74 | if getFirstFoundEnvTestBinaryDir() != "" { 75 | testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir() 76 | } 77 | 78 | // cfg is defined in this file globally. 79 | cfg, err = testEnv.Start() 80 | Expect(err).NotTo(HaveOccurred()) 81 | Expect(cfg).NotTo(BeNil()) 82 | 83 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 84 | Expect(err).NotTo(HaveOccurred()) 85 | Expect(k8sClient).NotTo(BeNil()) 86 | }) 87 | 88 | var _ = AfterSuite(func() { 89 | By("tearing down the test environment") 90 | cancel() 91 | err := testEnv.Stop() 92 | Expect(err).NotTo(HaveOccurred()) 93 | }) 94 | 95 | // getFirstFoundEnvTestBinaryDir locates the first binary in the specified path. 96 | // ENVTEST-based tests depend on specific binaries, usually located in paths set by 97 | // controller-runtime. When running tests directly (e.g., via an IDE) without using 98 | // Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured. 99 | // 100 | // This function streamlines the process by finding the required binaries, similar to 101 | // setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are 102 | // properly set up, run 'make setup-envtest' beforehand. 103 | func getFirstFoundEnvTestBinaryDir() string { 104 | basePath := filepath.Join("..", "..", "..", "bin", "k8s") 105 | entries, err := os.ReadDir(basePath) 106 | if err != nil { 107 | logf.Log.Error(err, "Failed to read directory", "path", basePath) 108 | return "" 109 | } 110 | for _, entry := range entries { 111 | if entry.IsDir() { 112 | return filepath.Join(basePath, entry.Name()) 113 | } 114 | } 115 | return "" 116 | } 117 | -------------------------------------------------------------------------------- /controllers/artifact/rulesfile/controller_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controller defines controllers' logic. 18 | 19 | package rulesfile 20 | 21 | import ( 22 | "context" 23 | 24 | . "github.com/onsi/ginkgo/v2" 25 | . "github.com/onsi/gomega" 26 | "k8s.io/apimachinery/pkg/api/errors" 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 | "k8s.io/apimachinery/pkg/types" 29 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 30 | 31 | artifactv1alpha1 "github.com/falcosecurity/falco-operator/api/artifact/v1alpha1" 32 | ) 33 | 34 | var _ = Describe("Rulesfile Controller", func() { 35 | Context("When reconciling a resource", func() { 36 | const resourceName = "test-resource" 37 | 38 | ctx := context.Background() 39 | 40 | typeNamespacedName := types.NamespacedName{ 41 | Name: resourceName, 42 | Namespace: "default", // TODO(user):Modify as needed 43 | } 44 | rulesfile := &artifactv1alpha1.Rulesfile{} 45 | 46 | BeforeEach(func() { 47 | By("creating the custom resource for the Kind Rulesfile") 48 | err := k8sClient.Get(ctx, typeNamespacedName, rulesfile) 49 | if err != nil && errors.IsNotFound(err) { 50 | resource := &artifactv1alpha1.Rulesfile{ 51 | ObjectMeta: metav1.ObjectMeta{ 52 | Name: resourceName, 53 | Namespace: "default", 54 | }, 55 | // TODO(user): Specify other spec details if needed. 56 | } 57 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 58 | } 59 | }) 60 | 61 | AfterEach(func() { 62 | // TODO(user): Cleanup logic after each test, like removing the resource instance. 63 | resource := &artifactv1alpha1.Rulesfile{} 64 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 65 | Expect(err).NotTo(HaveOccurred()) 66 | 67 | By("Cleanup the specific resource instance Rulesfile") 68 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 69 | }) 70 | It("should successfully reconcile the resource", func() { 71 | By("Reconciling the created resource") 72 | controllerReconciler := NewRulesfileReconciler(k8sClient, k8sClient.Scheme(), "test-node", "test-namespace") 73 | 74 | _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 75 | NamespacedName: typeNamespacedName, 76 | }) 77 | Expect(err).NotTo(HaveOccurred()) 78 | // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 79 | // Example: If you expect a certain status condition after reconciliation, verify it here. 80 | }) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /controllers/artifact/rulesfile/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package rulesfile defines the rulesfile controller logic. 18 | // It handles the lifecycle of the Rulesfile resource. 19 | package rulesfile 20 | -------------------------------------------------------------------------------- /controllers/artifact/rulesfile/suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controller defines controllers' logic. 18 | 19 | package rulesfile 20 | 21 | import ( 22 | "context" 23 | "os" 24 | "path/filepath" 25 | "testing" 26 | 27 | . "github.com/onsi/ginkgo/v2" 28 | . "github.com/onsi/gomega" 29 | "k8s.io/client-go/kubernetes/scheme" 30 | "k8s.io/client-go/rest" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | "sigs.k8s.io/controller-runtime/pkg/envtest" 33 | logf "sigs.k8s.io/controller-runtime/pkg/log" 34 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 35 | 36 | artifactv1alpha1 "github.com/falcosecurity/falco-operator/api/artifact/v1alpha1" 37 | ) 38 | 39 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 40 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 41 | 42 | var ( 43 | ctx context.Context 44 | cancel context.CancelFunc 45 | testEnv *envtest.Environment 46 | cfg *rest.Config 47 | k8sClient client.Client 48 | ) 49 | 50 | func TestControllers(t *testing.T) { 51 | RegisterFailHandler(Fail) 52 | 53 | RunSpecs(t, "Controller Suite") 54 | } 55 | 56 | var _ = BeforeSuite(func() { 57 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 58 | 59 | ctx, cancel = context.WithCancel(context.TODO()) 60 | 61 | var err error 62 | err = artifactv1alpha1.AddToScheme(scheme.Scheme) 63 | Expect(err).NotTo(HaveOccurred()) 64 | 65 | // +kubebuilder:scaffold:scheme 66 | 67 | By("bootstrapping test environment") 68 | testEnv = &envtest.Environment{ 69 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, 70 | ErrorIfCRDPathMissing: true, 71 | } 72 | 73 | // Retrieve the first found binary directory to allow running tests from IDEs 74 | if getFirstFoundEnvTestBinaryDir() != "" { 75 | testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir() 76 | } 77 | 78 | // cfg is defined in this file globally. 79 | cfg, err = testEnv.Start() 80 | Expect(err).NotTo(HaveOccurred()) 81 | Expect(cfg).NotTo(BeNil()) 82 | 83 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 84 | Expect(err).NotTo(HaveOccurred()) 85 | Expect(k8sClient).NotTo(BeNil()) 86 | }) 87 | 88 | var _ = AfterSuite(func() { 89 | By("tearing down the test environment") 90 | cancel() 91 | err := testEnv.Stop() 92 | Expect(err).NotTo(HaveOccurred()) 93 | }) 94 | 95 | // getFirstFoundEnvTestBinaryDir locates the first binary in the specified path. 96 | // ENVTEST-based tests depend on specific binaries, usually located in paths set by 97 | // controller-runtime. When running tests directly (e.g., via an IDE) without using 98 | // Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured. 99 | // 100 | // This function streamlines the process by finding the required binaries, similar to 101 | // setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are 102 | // properly set up, run 'make setup-envtest' beforehand. 103 | func getFirstFoundEnvTestBinaryDir() string { 104 | basePath := filepath.Join("..", "..", "..", "bin", "k8s") 105 | entries, err := os.ReadDir(basePath) 106 | if err != nil { 107 | logf.Log.Error(err, "Failed to read directory", "path", basePath) 108 | return "" 109 | } 110 | for _, entry := range entries { 111 | if entry.IsDir() { 112 | return filepath.Join(basePath, entry.Name()) 113 | } 114 | } 115 | return "" 116 | } 117 | -------------------------------------------------------------------------------- /controllers/falco/clusterRole.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | 22 | rbacv1 "k8s.io/api/rbac/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 29 | ) 30 | 31 | // generateClusterRole creates a ClusterRole resource for the given Falco instance. 32 | // It maps necessary permissions and sets it as an unstructured object. Returns the resource or an error. 33 | func generateClusterRole(ctx context.Context, cl client.Client, falco *instancev1alpha1.Falco) (*unstructured.Unstructured, error) { 34 | return generateResourceFromFalcoInstance(ctx, cl, falco, 35 | func(falco *instancev1alpha1.Falco) (runtime.Object, error) { 36 | resourceName := GenerateUniqueName(falco.Name, falco.Namespace) 37 | 38 | clusterRole := &rbacv1.ClusterRole{ 39 | TypeMeta: metav1.TypeMeta{ 40 | Kind: "ClusterRole", 41 | APIVersion: "rbac.authorization.k8s.io/v1", 42 | }, 43 | ObjectMeta: metav1.ObjectMeta{ 44 | Name: resourceName, 45 | Labels: falco.Labels, 46 | }, 47 | Rules: []rbacv1.PolicyRule{ 48 | { 49 | APIGroups: []string{""}, 50 | Resources: []string{"nodes"}, 51 | Verbs: []string{"get", "list", "watch"}, 52 | }, 53 | { 54 | APIGroups: []string{"rbac.authorization.k8s.io"}, 55 | Resources: []string{"clusterroles", "clusterrolebindings"}, 56 | Verbs: []string{"get", "list", "watch"}, 57 | }, 58 | }, 59 | } 60 | 61 | return clusterRole, nil 62 | }, 63 | generateOptions{ 64 | setControllerRef: false, 65 | isClusterScoped: true, 66 | }, 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /controllers/falco/clusterRoleBinding.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | 22 | rbacv1 "k8s.io/api/rbac/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 29 | ) 30 | 31 | // generateClusterRoleBinding creates a ClusterRoleBinding resource for the provided Falco instance in a Kubernetes cluster. 32 | // It associates a specified ServiceAccount with a ClusterRole and ensures the object is managed by the Falco instance. 33 | // The function converts the ClusterRoleBinding object to unstructured format and sets default values for it. 34 | func generateClusterRoleBinding(ctx context.Context, cl client.Client, falco *instancev1alpha1.Falco) (*unstructured.Unstructured, error) { 35 | return generateResourceFromFalcoInstance(ctx, cl, falco, 36 | func(falco *instancev1alpha1.Falco) (runtime.Object, error) { 37 | resourceName := GenerateUniqueName(falco.Name, falco.Namespace) 38 | 39 | return &rbacv1.ClusterRoleBinding{ 40 | TypeMeta: metav1.TypeMeta{ 41 | Kind: "ClusterRoleBinding", 42 | APIVersion: "rbac.authorization.k8s.io/v1", 43 | }, 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: resourceName, 46 | Labels: falco.Labels, 47 | }, 48 | Subjects: []rbacv1.Subject{ 49 | { 50 | Kind: "ServiceAccount", 51 | Name: falco.Name, 52 | Namespace: falco.Namespace, 53 | }, 54 | }, 55 | RoleRef: rbacv1.RoleRef{ 56 | Kind: "ClusterRole", 57 | Name: resourceName, 58 | APIGroup: "rbac.authorization.k8s.io", 59 | }, 60 | }, nil 61 | }, 62 | generateOptions{ 63 | setControllerRef: false, 64 | isClusterScoped: true, 65 | }, 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /controllers/falco/clusterRoleBinding_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | rbacv1 "k8s.io/api/rbac/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 29 | 30 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 31 | ) 32 | 33 | func TestGenerateClusterRoleBinding(t *testing.T) { 34 | tests := []struct { 35 | name string 36 | falco *instancev1alpha1.Falco 37 | expectedFields map[string]interface{} 38 | wantErr bool 39 | }{ 40 | { 41 | name: "basic cluster role binding", 42 | falco: &instancev1alpha1.Falco{ 43 | TypeMeta: metav1.TypeMeta{ 44 | APIVersion: "instance.falcosecurity.dev/v1alpha1", 45 | Kind: "Falco", 46 | }, 47 | ObjectMeta: metav1.ObjectMeta{ 48 | Name: "test-falco", 49 | Namespace: "test-namespace", 50 | Labels: map[string]string{ 51 | "app": "falco", 52 | }, 53 | }, 54 | }, 55 | expectedFields: map[string]interface{}{ 56 | "apiVersion": "rbac.authorization.k8s.io/v1", 57 | "kind": "ClusterRoleBinding", 58 | "metadata": map[string]interface{}{ 59 | "name": "test-falco--test-namespace", 60 | "labels": map[string]interface{}{ 61 | "app": "falco", 62 | }, 63 | }, 64 | "subjects": []interface{}{ 65 | map[string]interface{}{ 66 | "kind": "ServiceAccount", 67 | "name": "test-falco", 68 | "namespace": "test-namespace", 69 | }, 70 | }, 71 | "roleRef": map[string]interface{}{ 72 | "kind": "ClusterRole", 73 | "name": "test-falco--test-namespace", 74 | "apiGroup": "rbac.authorization.k8s.io", 75 | }, 76 | }, 77 | wantErr: false, 78 | }, 79 | { 80 | name: "cluster role binding with multiple labels", 81 | falco: &instancev1alpha1.Falco{ 82 | TypeMeta: metav1.TypeMeta{ 83 | APIVersion: "instance.falcosecurity.dev/v1alpha1", 84 | Kind: "Falco", 85 | }, 86 | ObjectMeta: metav1.ObjectMeta{ 87 | 88 | Name: "test-falco", 89 | Namespace: "test-namespace", 90 | Labels: map[string]string{ 91 | "app": "falco", 92 | "version": "v1", 93 | "env": "test", 94 | }, 95 | }, 96 | }, 97 | expectedFields: map[string]interface{}{ 98 | "metadata": map[string]interface{}{ 99 | "name": "test-falco--test-namespace", 100 | "labels": map[string]interface{}{ 101 | "app": "falco", 102 | "version": "v1", 103 | "env": "test", 104 | }, 105 | }, 106 | }, 107 | wantErr: false, 108 | }, 109 | } 110 | 111 | scheme := runtime.NewScheme() 112 | _ = rbacv1.AddToScheme(scheme) 113 | _ = instancev1alpha1.AddToScheme(scheme) 114 | 115 | for _, tt := range tests { 116 | t.Run(tt.name, func(t *testing.T) { 117 | client := fake.NewClientBuilder().WithScheme(scheme).Build() 118 | 119 | result, err := generateClusterRoleBinding(context.Background(), client, tt.falco) 120 | 121 | if tt.wantErr { 122 | assert.Error(t, err) 123 | return 124 | } 125 | 126 | assert.NoError(t, err) 127 | assert.NotNil(t, result) 128 | 129 | // Verify TypeMeta. 130 | assert.Equal(t, "ClusterRoleBinding", result.GetKind()) 131 | assert.Equal(t, "rbac.authorization.k8s.io/v1", result.GetAPIVersion()) 132 | 133 | // Verify basic metadata. 134 | assert.Equal(t, GenerateUniqueName(tt.falco.Name, tt.falco.Namespace), result.GetName()) 135 | assert.Equal(t, "", result.GetNamespace()) 136 | assert.Equal(t, tt.falco.Labels, result.GetLabels()) 137 | 138 | // Verify subjects. 139 | subjects, found, err := unstructured.NestedSlice(result.Object, "subjects") 140 | assert.NoError(t, err) 141 | assert.True(t, found) 142 | assert.Len(t, subjects, 1) 143 | subject := subjects[0].(map[string]interface{}) 144 | assert.Equal(t, "ServiceAccount", subject["kind"]) 145 | assert.Equal(t, tt.falco.Name, subject["name"]) 146 | assert.Equal(t, tt.falco.Namespace, subject["namespace"]) 147 | 148 | // Verify roleRef. 149 | roleRef, found, err := unstructured.NestedMap(result.Object, "roleRef") 150 | assert.NoError(t, err) 151 | assert.True(t, found) 152 | assert.Equal(t, "ClusterRole", roleRef["kind"]) 153 | assert.Equal(t, GenerateUniqueName(tt.falco.Name, tt.falco.Namespace), roleRef["name"]) 154 | assert.Equal(t, "rbac.authorization.k8s.io", roleRef["apiGroup"]) 155 | 156 | // Verify additional expected fields if specified 157 | for key, expectedValue := range tt.expectedFields { 158 | value, found, err := unstructured.NestedFieldNoCopy(result.Object, key) 159 | assert.NoError(t, err) 160 | assert.True(t, found) 161 | assert.Equal(t, expectedValue, value) 162 | } 163 | }) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /controllers/falco/clusterRole_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | rbacv1 "k8s.io/api/rbac/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 | "k8s.io/apimachinery/pkg/runtime" 29 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 30 | 31 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 32 | ) 33 | 34 | func TestGenerateClusterRole(t *testing.T) { 35 | // Create a fake client with the necessary schemes 36 | scheme := runtime.NewScheme() 37 | require.NoError(t, rbacv1.AddToScheme(scheme)) 38 | require.NoError(t, instancev1alpha1.AddToScheme(scheme)) 39 | 40 | tests := []struct { 41 | name string 42 | falco *instancev1alpha1.Falco 43 | verify func(*testing.T, *unstructured.Unstructured) 44 | wantErr bool 45 | }{ 46 | { 47 | name: "Basic ClusterRole creation", 48 | falco: &instancev1alpha1.Falco{ 49 | ObjectMeta: metav1.ObjectMeta{ 50 | Name: "test-falco", 51 | Namespace: "default", 52 | Labels: map[string]string{ 53 | "app": "falco", 54 | }, 55 | }, 56 | }, 57 | verify: func(t *testing.T, obj *unstructured.Unstructured) { 58 | assert.Equal(t, "ClusterRole", obj.GetKind()) 59 | assert.Equal(t, "rbac.authorization.k8s.io/v1", obj.GetAPIVersion()) 60 | assert.Equal(t, "test-falco--default", obj.GetName()) 61 | assert.Equal(t, map[string]string{"app": "falco"}, obj.GetLabels()) 62 | 63 | // Verify rules 64 | rules, found, err := unstructured.NestedSlice(obj.Object, "rules") 65 | require.NoError(t, err) 66 | require.True(t, found) 67 | require.Len(t, rules, 2) 68 | 69 | // Check first rule (nodes) 70 | rule0 := rules[0].(map[string]interface{}) 71 | assert.Equal(t, []interface{}{""}, rule0["apiGroups"]) 72 | assert.Equal(t, []interface{}{"nodes"}, rule0["resources"]) 73 | assert.Equal(t, []interface{}{"get", "list", "watch"}, rule0["verbs"]) 74 | 75 | // Check second rule (RBAC) 76 | rule1 := rules[1].(map[string]interface{}) 77 | assert.Equal(t, []interface{}{"rbac.authorization.k8s.io"}, rule1["apiGroups"]) 78 | assert.Equal(t, []interface{}{"clusterroles", "clusterrolebindings"}, rule1["resources"]) 79 | assert.Equal(t, []interface{}{"get", "list", "watch"}, rule1["verbs"]) 80 | }, 81 | wantErr: false, 82 | }, 83 | { 84 | name: "ClusterRole with empty namespace", 85 | falco: &instancev1alpha1.Falco{ 86 | ObjectMeta: metav1.ObjectMeta{ 87 | Name: "test-falco", 88 | }, 89 | }, 90 | verify: func(t *testing.T, obj *unstructured.Unstructured) { 91 | assert.Equal(t, "test-falco--", obj.GetName()) 92 | }, 93 | wantErr: false, 94 | }, 95 | { 96 | name: "ClusterRole with no labels", 97 | falco: &instancev1alpha1.Falco{ 98 | ObjectMeta: metav1.ObjectMeta{ 99 | Name: "test-falco", 100 | Namespace: "default", 101 | }, 102 | }, 103 | verify: func(t *testing.T, obj *unstructured.Unstructured) { 104 | labels := obj.GetLabels() 105 | assert.Empty(t, labels) 106 | }, 107 | wantErr: false, 108 | }, 109 | } 110 | 111 | for _, tt := range tests { 112 | t.Run(tt.name, func(t *testing.T) { 113 | // Create a new client for each test 114 | cl := fake.NewClientBuilder().WithScheme(scheme).Build() 115 | 116 | // Execute the function 117 | obj, err := generateClusterRole(context.Background(), cl, tt.falco) 118 | 119 | if tt.wantErr { 120 | require.Error(t, err) 121 | return 122 | } 123 | 124 | require.NoError(t, err) 125 | require.NotNil(t, obj) 126 | 127 | // Run the verification function 128 | tt.verify(t, obj) 129 | }) 130 | } 131 | } 132 | 133 | // TestGenerateClusterRoleErrors tests error scenarios. 134 | func TestGenerateClusterRoleErrors(t *testing.T) { 135 | // Create a failing client that always returns an error 136 | failingClient := &mockFailingClient{} 137 | 138 | falco := &instancev1alpha1.Falco{ 139 | ObjectMeta: metav1.ObjectMeta{ 140 | Name: "test-falco", 141 | Namespace: "default", 142 | }, 143 | } 144 | 145 | _, err := generateClusterRole(context.Background(), failingClient, falco) 146 | assert.Error(t, err) 147 | } 148 | -------------------------------------------------------------------------------- /controllers/falco/conditions.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | 22 | commonv1alpha1 "github.com/falcosecurity/falco-operator/api/common/v1alpha1" 23 | ) 24 | 25 | // findCondition searches for a condition of the specified type in the given slice of conditions. 26 | // It returns a pointer to the condition if found, otherwise it returns nil. 27 | func findCondition(conditions []metav1.Condition, conditionType commonv1alpha1.ConditionType) *metav1.Condition { 28 | for i := range conditions { 29 | if conditions[i].Type == string(conditionType) { 30 | return &conditions[i] 31 | } 32 | } 33 | return nil 34 | } 35 | 36 | // updateConditions updates the given slice of conditions with the new conditions provided. 37 | // If a condition of the same type already exists, it updates the existing condition. 38 | // If the status of the condition has not changed, it retains the original LastTransitionTime. 39 | func updateConditions(conditions []metav1.Condition, newConditions ...metav1.Condition) []metav1.Condition { 40 | ret := make([]metav1.Condition, 0, len(conditions)) 41 | 42 | for _, nc := range newConditions { 43 | c := findCondition(conditions, commonv1alpha1.ConditionType(nc.Type)) 44 | if c == nil { 45 | ret = append(ret, nc) 46 | continue 47 | } 48 | 49 | if nc.Status == c.Status { 50 | nc.LastTransitionTime = c.LastTransitionTime 51 | } 52 | ret = append(ret, nc) 53 | } 54 | 55 | return ret 56 | } 57 | -------------------------------------------------------------------------------- /controllers/falco/configmap.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | corev1 "k8s.io/api/core/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 30 | ) 31 | 32 | // generateRoleBinding returns a RoleBinding for Falco. 33 | func generateConfigmap(ctx context.Context, cl client.Client, falco *instancev1alpha1.Falco) (*unstructured.Unstructured, error) { 34 | return generateResourceFromFalcoInstance(ctx, cl, falco, 35 | func(falco *instancev1alpha1.Falco) (runtime.Object, error) { 36 | var config string 37 | 38 | switch falco.Spec.Type { 39 | case resourceTypeDeployment: 40 | config = deploymentFalcoConfig 41 | case resourceTypeDaemonSet: 42 | config = daemonsetFalcoConfig 43 | default: 44 | return nil, fmt.Errorf("unsupported falco type: %s", falco.Spec.Type) 45 | } 46 | 47 | cm := &corev1.ConfigMap{ 48 | TypeMeta: metav1.TypeMeta{ 49 | Kind: "ConfigMap", 50 | APIVersion: "v1", 51 | }, 52 | ObjectMeta: metav1.ObjectMeta{ 53 | Name: falco.Name, 54 | Namespace: falco.Namespace, 55 | Labels: falco.Labels, 56 | }, 57 | Data: map[string]string{ 58 | "falco.yaml": config, 59 | }, 60 | } 61 | 62 | return cm, nil 63 | }, 64 | generateOptions{ 65 | setControllerRef: true, 66 | isClusterScoped: false, 67 | }, 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /controllers/falco/configmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 29 | 30 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 31 | ) 32 | 33 | func TestGenerateConfigmap(t *testing.T) { 34 | // Create a new scheme and add the necessary types 35 | scheme := runtime.NewScheme() 36 | _ = corev1.AddToScheme(scheme) 37 | _ = instancev1alpha1.AddToScheme(scheme) 38 | 39 | tests := []struct { 40 | name string 41 | falco *instancev1alpha1.Falco 42 | wantErr bool 43 | errContains string 44 | }{ 45 | { 46 | name: "deployment type configmap", 47 | falco: &instancev1alpha1.Falco{ 48 | TypeMeta: metav1.TypeMeta{ 49 | APIVersion: instancev1alpha1.GroupVersion.String(), 50 | Kind: "Falco", 51 | }, 52 | ObjectMeta: metav1.ObjectMeta{ 53 | Name: "test-falco", 54 | Namespace: "default", 55 | Labels: map[string]string{ 56 | "app": "falco", 57 | }, 58 | }, 59 | Spec: instancev1alpha1.FalcoSpec{ 60 | Type: resourceTypeDeployment, 61 | }, 62 | }, 63 | wantErr: false, 64 | }, 65 | { 66 | name: "daemonset type configmap", 67 | falco: &instancev1alpha1.Falco{ 68 | TypeMeta: metav1.TypeMeta{ 69 | APIVersion: instancev1alpha1.GroupVersion.String(), 70 | Kind: "Falco", 71 | }, 72 | ObjectMeta: metav1.ObjectMeta{ 73 | Name: "test-falco", 74 | Namespace: "default", 75 | Labels: map[string]string{ 76 | "app": "falco", 77 | }, 78 | }, 79 | Spec: instancev1alpha1.FalcoSpec{ 80 | Type: resourceTypeDaemonSet, 81 | }, 82 | }, 83 | wantErr: false, 84 | }, 85 | { 86 | name: "invalid type configmap", 87 | falco: &instancev1alpha1.Falco{ 88 | TypeMeta: metav1.TypeMeta{ 89 | APIVersion: instancev1alpha1.GroupVersion.String(), 90 | Kind: "Falco", 91 | }, 92 | ObjectMeta: metav1.ObjectMeta{ 93 | Name: "test-falco", 94 | Namespace: "default", 95 | }, 96 | Spec: instancev1alpha1.FalcoSpec{ 97 | Type: "invalid-type", 98 | }, 99 | }, 100 | wantErr: true, 101 | errContains: "unsupported falco type", 102 | }, 103 | } 104 | 105 | for _, tt := range tests { 106 | t.Run(tt.name, func(t *testing.T) { 107 | // Create a fake client. 108 | client := fake.NewClientBuilder().WithScheme(scheme).Build() 109 | 110 | // Call the function. 111 | result, err := generateConfigmap(context.Background(), client, tt.falco) 112 | 113 | if tt.wantErr { 114 | assert.Error(t, err) 115 | if tt.errContains != "" { 116 | assert.Contains(t, err.Error(), tt.errContains) 117 | } 118 | assert.Nil(t, result) 119 | return 120 | } 121 | 122 | assert.NoError(t, err) 123 | assert.NotNil(t, result) 124 | 125 | // Verify the basic structure of the configmap. 126 | name, _, err := unstructured.NestedString(result.Object, "metadata", "name") 127 | assert.NoError(t, err) 128 | assert.Equal(t, tt.falco.Name, name) 129 | 130 | namespace, _, err := unstructured.NestedString(result.Object, "metadata", "namespace") 131 | assert.NoError(t, err) 132 | assert.Equal(t, tt.falco.Namespace, namespace) 133 | 134 | // Verify the config data exists. 135 | data, _, err := unstructured.NestedStringMap(result.Object, "data") 136 | assert.NoError(t, err) 137 | assert.Contains(t, data, "falco.yaml") 138 | 139 | // Verify config content based on type. 140 | expectedConfig := "" 141 | switch tt.falco.Spec.Type { 142 | case resourceTypeDeployment: 143 | expectedConfig = deploymentFalcoConfig 144 | case resourceTypeDaemonSet: 145 | expectedConfig = daemonsetFalcoConfig 146 | } 147 | assert.Equal(t, expectedConfig, data["falco.yaml"]) 148 | 149 | // Verify labels. 150 | labels, _, err := unstructured.NestedStringMap(result.Object, "metadata", "labels") 151 | assert.NoError(t, err) 152 | assert.Equal(t, tt.falco.Labels, labels) 153 | 154 | // Verify controller reference. 155 | ownerRefs, exists, err := unstructured.NestedSlice(result.Object, "metadata", "ownerReferences") 156 | assert.NoError(t, err) 157 | assert.True(t, exists) 158 | assert.Len(t, ownerRefs, 1) 159 | 160 | ownerRef := ownerRefs[0].(map[string]interface{}) 161 | assert.Equal(t, tt.falco.Name, ownerRef["name"]) 162 | assert.Equal(t, tt.falco.Kind, ownerRef["kind"]) 163 | assert.Equal(t, tt.falco.APIVersion, ownerRef["apiVersion"]) 164 | assert.Equal(t, string(tt.falco.UID), ownerRef["uid"]) 165 | assert.True(t, ownerRef["controller"].(bool)) 166 | assert.True(t, ownerRef["blockOwnerDeletion"].(bool)) 167 | }) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /controllers/falco/controller_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | 22 | . "github.com/onsi/ginkgo/v2" 23 | . "github.com/onsi/gomega" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/types" 27 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 28 | 29 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 30 | ) 31 | 32 | var _ = Describe("Falco Controller", Ordered, func() { 33 | Context("When reconciling a resource", func() { 34 | const resourceName = "test-resource" 35 | 36 | ctx := context.Background() 37 | 38 | typeNamespacedName := types.NamespacedName{ 39 | Name: resourceName, 40 | Namespace: "default", // TODO(user):Modify as needed 41 | } 42 | falco := &instancev1alpha1.Falco{} 43 | 44 | BeforeEach(func() { 45 | By("creating the custom resource for the Kind Falco") 46 | err := k8sClient.Get(ctx, typeNamespacedName, falco) 47 | if err != nil && errors.IsNotFound(err) { 48 | resource := &instancev1alpha1.Falco{ 49 | ObjectMeta: metav1.ObjectMeta{ 50 | Name: resourceName, 51 | Namespace: "default", 52 | }, 53 | Spec: instancev1alpha1.FalcoSpec{ 54 | Replicas: nil, 55 | PodTemplateSpec: nil, 56 | }, 57 | } 58 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 59 | } 60 | }) 61 | 62 | AfterEach(func() { 63 | // TODO(user): Cleanup logic after each test, like removing the resource instance. 64 | resource := &instancev1alpha1.Falco{} 65 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | By("Cleanup the specific resource instance Falco") 69 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 70 | }) 71 | It("should successfully reconcile the resource", func() { 72 | By("Reconciling the created resource") 73 | controllerReconciler := &Reconciler{ 74 | Client: k8sClient, 75 | Scheme: k8sClient.Scheme(), 76 | } 77 | 78 | _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ 79 | NamespacedName: typeNamespacedName, 80 | }) 81 | Expect(err).NotTo(HaveOccurred()) 82 | // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. 83 | // Example: If you expect a certain status condition after reconciliation, verify it here. 84 | }) 85 | }) 86 | 87 | Context("When creating an empty CRD", func() { 88 | const resourceName = "empty-crd" 89 | 90 | ctx := context.Background() 91 | 92 | typeNamespacedName := types.NamespacedName{ 93 | Name: resourceName, 94 | Namespace: "default", 95 | } 96 | falco := &instancev1alpha1.Falco{} 97 | 98 | BeforeAll(func() { 99 | By("creating the custom resource for the Kind Falco") 100 | err := k8sClient.Get(ctx, typeNamespacedName, falco) 101 | if err != nil && errors.IsNotFound(err) { 102 | resource := &instancev1alpha1.Falco{ 103 | ObjectMeta: metav1.ObjectMeta{ 104 | Name: resourceName, 105 | Namespace: "default", 106 | }, 107 | Spec: instancev1alpha1.FalcoSpec{ 108 | Replicas: nil, 109 | PodTemplateSpec: nil, 110 | }, 111 | } 112 | Expect(k8sClient.Create(ctx, resource)).To(Succeed()) 113 | } 114 | }) 115 | 116 | AfterAll(func() { 117 | resource := &instancev1alpha1.Falco{} 118 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 119 | Expect(err).NotTo(HaveOccurred()) 120 | 121 | By("Cleanup the specific resource instance Falco") 122 | Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) 123 | }) 124 | 125 | It("type should be set to DaemonSet", func() { 126 | resource := &instancev1alpha1.Falco{} 127 | By("Getting the created resource") 128 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 129 | Expect(err).NotTo(HaveOccurred()) 130 | Expect(resource.Spec.Type).To(BeEquivalentTo("DaemonSet")) 131 | }) 132 | 133 | It("replicas should be set to 1", func() { 134 | resource := &instancev1alpha1.Falco{} 135 | By("Getting the created resource") 136 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 137 | Expect(err).NotTo(HaveOccurred()) 138 | Expect(*resource.Spec.Replicas).To(BeEquivalentTo(1)) 139 | }) 140 | 141 | It("version should be set to empty string", func() { 142 | resource := &instancev1alpha1.Falco{} 143 | By("Getting the created resource") 144 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 145 | Expect(err).NotTo(HaveOccurred()) 146 | Expect(resource.Spec.Version).To(BeEmpty()) 147 | }) 148 | 149 | It("podTemplateSpec should be set to nil", func() { 150 | resource := &instancev1alpha1.Falco{} 151 | By("Getting the created resource") 152 | err := k8sClient.Get(ctx, typeNamespacedName, resource) 153 | Expect(err).NotTo(HaveOccurred()) 154 | Expect(resource.Spec.PodTemplateSpec).To(BeNil()) 155 | }) 156 | }) 157 | }) 158 | -------------------------------------------------------------------------------- /controllers/falco/diff.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | "sigs.k8s.io/structured-merge-diff/v4/typed" 26 | 27 | "github.com/falcosecurity/falco-operator/internal/pkg/scheme" 28 | ) 29 | 30 | // diff calculates the difference between the current and desired objects. 31 | // It accepts either unstructured.Unstructured objects or typed objects that can be converted to unstructured. 32 | func diff(current, desired interface{}) (*typed.Comparison, error) { 33 | // Convert inputs to unstructured if needed 34 | currentUnstructured, err := toUnstructured(current) 35 | if err != nil { 36 | return nil, fmt.Errorf("failed to convert current object to unstructured: %w", err) 37 | } 38 | 39 | desiredUnstructured, err := toUnstructured(desired) 40 | if err != nil { 41 | return nil, fmt.Errorf("failed to convert desired object to unstructured: %w", err) 42 | } 43 | 44 | // Create a parser to compare the resources 45 | parser := scheme.Parser() 46 | 47 | currentTypePath := getTypePath(currentUnstructured) 48 | 49 | // Parse the base resource 50 | currentTyped, err := parser.Type(currentTypePath).FromUnstructured(currentUnstructured.Object) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | desiredTypePath := getTypePath(desiredUnstructured) 56 | // Parse the user defined resource 57 | desiredTyped, err := parser.Type(desiredTypePath).FromUnstructured(desiredUnstructured.Object) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | return currentTyped.Compare(desiredTyped) 63 | } 64 | 65 | // getTypePath returns the schema type path for an unstructured object. 66 | func getTypePath(obj *unstructured.Unstructured) string { 67 | apiVersion := obj.GetAPIVersion() 68 | resourceType := obj.GetKind() 69 | gv := strings.Split(apiVersion, "/") 70 | 71 | // Build the schema path based on whether it's a core resource or not 72 | var typePath string 73 | if len(gv) == 1 { 74 | // Core resources like v1 have no group 75 | typePath = fmt.Sprintf("io.k8s.api.core.%s.%s", gv[0], resourceType) 76 | } else { 77 | // Other resources have group and version 78 | typePath = fmt.Sprintf("io.k8s.api.%s.%s.%s", apiGroupToSchemaGroup(gv[0]), gv[1], resourceType) 79 | } 80 | 81 | return typePath 82 | } 83 | 84 | func apiGroupToSchemaGroup(apiGroup string) string { 85 | mappings := map[string]string{ 86 | "rbac.authorization.k8s.io": "rbac", 87 | "networking.k8s.io": "networking", 88 | "certificates.k8s.io": "certificates", 89 | "storage.k8s.io": "storage", 90 | "admissionregistration.k8s.io": "admissionregistration", 91 | "scheduling.k8s.io": "scheduling", 92 | "coordination.k8s.io": "coordination", 93 | "discovery.k8s.io": "discovery", 94 | } 95 | 96 | if mapped, ok := mappings[apiGroup]; ok { 97 | return mapped 98 | } 99 | 100 | return apiGroup 101 | } 102 | 103 | // toUnstructured converts an object to an unstructured.Unstructured. 104 | func toUnstructured(obj interface{}) (*unstructured.Unstructured, error) { 105 | // If it's already unstructured, just return it 106 | if u, ok := obj.(*unstructured.Unstructured); ok { 107 | return u, nil 108 | } 109 | 110 | // Convert the typed object to unstructured 111 | unstructuredObj := &unstructured.Unstructured{} 112 | data, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) 113 | if err != nil { 114 | return nil, err 115 | } 116 | unstructuredObj.SetUnstructuredContent(data) 117 | 118 | return unstructuredObj, nil 119 | } 120 | -------------------------------------------------------------------------------- /controllers/falco/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package falco defines falco's controllers logic. 18 | package falco 19 | -------------------------------------------------------------------------------- /controllers/falco/mockClient.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | corev1 "k8s.io/api/core/v1" 24 | rbacv1 "k8s.io/api/rbac/v1" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 29 | ) 30 | 31 | // mockFailingClient implements client.Client and always returns errors. 32 | type mockFailingClient struct { 33 | client.Client 34 | scheme *runtime.Scheme 35 | } 36 | 37 | func newMockFailingClient() *mockFailingClient { 38 | scheme := runtime.NewScheme() 39 | _ = corev1.AddToScheme(scheme) 40 | _ = rbacv1.AddToScheme(scheme) 41 | _ = instancev1alpha1.AddToScheme(scheme) 42 | return &mockFailingClient{scheme: scheme} 43 | } 44 | 45 | func (m *mockFailingClient) Create(_ context.Context, _ client.Object, _ ...client.CreateOption) error { 46 | return fmt.Errorf("mock error") 47 | } 48 | 49 | func (m *mockFailingClient) Update(_ context.Context, _ client.Object, _ ...client.UpdateOption) error { 50 | return fmt.Errorf("mock error") 51 | } 52 | 53 | func (m *mockFailingClient) Scheme() *runtime.Scheme { 54 | return m.scheme 55 | } 56 | -------------------------------------------------------------------------------- /controllers/falco/nameGenerator.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | ) 23 | 24 | const ( 25 | delimiter = "--" 26 | escapedDelimiter = "__DASH__" 27 | ) 28 | 29 | // GenerateUniqueName creates a unique name from the name and namespace of a Falco instance. 30 | func GenerateUniqueName(name, namespace string) string { 31 | escapedName := strings.ReplaceAll(name, delimiter, escapedDelimiter) 32 | escapedNamespace := strings.ReplaceAll(namespace, delimiter, escapedDelimiter) 33 | return fmt.Sprintf("%s%s%s", escapedName, delimiter, escapedNamespace) 34 | } 35 | 36 | // ParseUniqueName reverses the unique name back into the original name and namespace. 37 | func ParseUniqueName(uniqueName string) (name, namespace string, err error) { 38 | // Check if the unique name contains more than one delimiter. 39 | // If it does, we can't parse it correctly since it has not been generated by us. 40 | if count := strings.Count(uniqueName, delimiter); count != 1 { 41 | return "", "", fmt.Errorf("invalid unique name format, was expecting only one %q: %s", delimiter, uniqueName) 42 | } 43 | 44 | // If the name contains only the delimiter, we need to handle it as a special case. 45 | if uniqueName == delimiter { 46 | return "", "", fmt.Errorf("invalid unique name format, was expecting a name: %s", uniqueName) 47 | } 48 | 49 | // Split the unique name into the original name and namespace. 50 | parts := strings.SplitN(uniqueName, delimiter, 2) 51 | if len(parts) != 2 { 52 | return "", "", fmt.Errorf("invalid unique name format: %s", uniqueName) 53 | } 54 | 55 | // Replace the escaped delimiter with the original delimiter in both parts. 56 | originalName := strings.ReplaceAll(parts[0], escapedDelimiter, delimiter) 57 | originalNamespace := strings.ReplaceAll(parts[1], escapedDelimiter, delimiter) 58 | return originalName, originalNamespace, nil 59 | } 60 | -------------------------------------------------------------------------------- /controllers/falco/nameGenerator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func Test_generatesUniqueNameCorrectly(t *testing.T) { 26 | tests := []struct { 27 | name string 28 | namespace string 29 | expected string 30 | }{ 31 | {"simple", "default", "simple--default"}, 32 | {"name--with--dashes", "namespace--with--dashes", "name__DASH__with__DASH__dashes--namespace__DASH__with__DASH__dashes"}, 33 | {"", "namespace", "--namespace"}, 34 | {"name", "", "name--"}, 35 | {"name--", "namespace--", "name__DASH__--namespace__DASH__"}, 36 | } 37 | 38 | for _, tt := range tests { 39 | t.Run(tt.name+"_"+tt.namespace, func(t *testing.T) { 40 | result := GenerateUniqueName(tt.name, tt.namespace) 41 | assert.Equal(t, tt.expected, result) 42 | }) 43 | } 44 | } 45 | 46 | func Test_parsesUniqueNameCorrectly(t *testing.T) { 47 | tests := []struct { 48 | uniqueName string 49 | expectedName string 50 | expectedNamespace string 51 | expectError bool 52 | }{ 53 | {"simple--default", "simple", "default", false}, 54 | {"name__DASH__with__DASH__dashes--namespace__DASH__with__DASH__dashes", "name--with--dashes", "namespace--with--dashes", false}, 55 | {"--namespace", "", "namespace", false}, 56 | {"name--", "name", "", false}, 57 | {"invalid-name", "", "", true}, 58 | {"name__DASH__--namespace__DASH__", "name--", "namespace--", false}, 59 | } 60 | 61 | for _, tt := range tests { 62 | t.Run(tt.uniqueName, func(t *testing.T) { 63 | name, namespace, err := ParseUniqueName(tt.uniqueName) 64 | if tt.expectError { 65 | assert.Error(t, err) 66 | } else { 67 | assert.NoError(t, err) 68 | assert.Equal(t, tt.expectedName, name) 69 | assert.Equal(t, tt.expectedNamespace, namespace) 70 | } 71 | }) 72 | } 73 | } 74 | 75 | func Test_handlesInvalidUniqueNameFormats(t *testing.T) { 76 | tests := []struct { 77 | uniqueName string 78 | expectError bool 79 | }{ 80 | {"", true}, 81 | {"--", true}, 82 | {"invalid--name--format--", true}, 83 | {"name--namespace--extra", true}, 84 | } 85 | 86 | for _, tt := range tests { 87 | t.Run(tt.uniqueName, func(t *testing.T) { 88 | _, _, err := ParseUniqueName(tt.uniqueName) 89 | if tt.expectError { 90 | assert.Error(t, err) 91 | } else { 92 | assert.NoError(t, err) 93 | } 94 | }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /controllers/falco/resourceHandlers.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | 22 | rbacv1 "k8s.io/api/rbac/v1" 23 | "k8s.io/apimachinery/pkg/types" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | "sigs.k8s.io/controller-runtime/pkg/log" 26 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | ) 28 | 29 | // clusterScopedResourceHandler used to handle cluster-scoped resources like ClusterRole and ClusterRoleBinding. 30 | // It returns a list of reconcile.Requests for the Falco instance associated with the resource. 31 | func clusterScopedResourceHandler(ctx context.Context, obj client.Object) []reconcile.Request { 32 | var ns types.NamespacedName 33 | 34 | logger := log.FromContext(ctx) 35 | 36 | switch obj.(type) { 37 | case *rbacv1.ClusterRoleBinding, *rbacv1.ClusterRole: 38 | // We extract the Falco instance name and namespace from the resource name. 39 | name, namespace, err := ParseUniqueName(obj.GetName()) 40 | ns = types.NamespacedName{ 41 | Name: name, 42 | Namespace: namespace, 43 | } 44 | 45 | if err != nil { 46 | logger.V(5).Info("Failed to parse unique name", "name", obj.GetName(), "error", err) 47 | return nil 48 | } 49 | 50 | default: 51 | return nil 52 | } 53 | 54 | return []reconcile.Request{ 55 | { 56 | NamespacedName: ns, 57 | }, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /controllers/falco/resourceHandlers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | rbacv1 "k8s.io/api/rbac/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/types" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 29 | ) 30 | 31 | func TestClusterScopedResourceHandler(t *testing.T) { 32 | ctx := context.Background() 33 | 34 | tests := []struct { 35 | name string 36 | obj client.Object 37 | expected []reconcile.Request 38 | }{ 39 | { 40 | name: "ClusterRoleBinding with valid name", 41 | obj: &rbacv1.ClusterRoleBinding{ 42 | ObjectMeta: metav1.ObjectMeta{ 43 | Name: "falco-test--default", 44 | }, 45 | }, 46 | expected: []reconcile.Request{ 47 | { 48 | NamespacedName: types.NamespacedName{ 49 | Name: "falco-test", 50 | Namespace: "default", 51 | }, 52 | }, 53 | }, 54 | }, 55 | { 56 | name: "ClusterRole with valid name", 57 | obj: &rbacv1.ClusterRole{ 58 | ObjectMeta: metav1.ObjectMeta{ 59 | Name: "falco-test--custom-ns", 60 | }, 61 | }, 62 | expected: []reconcile.Request{ 63 | { 64 | NamespacedName: types.NamespacedName{ 65 | Name: "falco-test", 66 | Namespace: "custom-ns", 67 | }, 68 | }, 69 | }, 70 | }, 71 | { 72 | name: "ClusterRoleBinding with invalid name format", 73 | obj: &rbacv1.ClusterRoleBinding{ 74 | ObjectMeta: metav1.ObjectMeta{ 75 | Name: "invalid-name-format", 76 | }, 77 | }, 78 | expected: nil, 79 | }, 80 | { 81 | name: "Unsupported resource type", 82 | obj: &rbacv1.Role{ // Using Role as an unsupported type 83 | ObjectMeta: metav1.ObjectMeta{ 84 | Name: "test-name--default", 85 | }, 86 | }, 87 | expected: nil, 88 | }, 89 | } 90 | 91 | for _, tt := range tests { 92 | t.Run(tt.name, func(t *testing.T) { 93 | result := clusterScopedResourceHandler(ctx, tt.obj) 94 | assert.Equal(t, tt.expected, result) 95 | }) 96 | } 97 | } 98 | 99 | func TestClusterScopedResourceHandlerEdgeCases(t *testing.T) { 100 | ctx := context.Background() 101 | 102 | tests := []struct { 103 | name string 104 | obj client.Object 105 | expected []reconcile.Request 106 | }{ 107 | { 108 | name: "Empty name", 109 | obj: &rbacv1.ClusterRoleBinding{ 110 | ObjectMeta: metav1.ObjectMeta{ 111 | Name: "", 112 | }, 113 | }, 114 | expected: nil, 115 | }, 116 | { 117 | name: "No name at all", 118 | obj: &rbacv1.ClusterRoleBinding{ 119 | ObjectMeta: metav1.ObjectMeta{ 120 | Name: "", 121 | }, 122 | }, 123 | expected: nil, 124 | }, 125 | } 126 | 127 | for _, tt := range tests { 128 | t.Run(tt.name, func(t *testing.T) { 129 | result := clusterScopedResourceHandler(ctx, tt.obj) 130 | assert.Equal(t, tt.expected, result) 131 | }) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /controllers/falco/role.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | 22 | rbacv1 "k8s.io/api/rbac/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | artifactv1alpha1 "github.com/falcosecurity/falco-operator/api/artifact/v1alpha1" 29 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 30 | ) 31 | 32 | // generateRole returns a Role for Falco. 33 | func generateRole(ctx context.Context, cl client.Client, falco *instancev1alpha1.Falco) (*unstructured.Unstructured, error) { 34 | return generateResourceFromFalcoInstance(ctx, cl, falco, 35 | func(falco *instancev1alpha1.Falco) (runtime.Object, error) { 36 | role := &rbacv1.Role{ 37 | TypeMeta: metav1.TypeMeta{ 38 | Kind: "Role", 39 | APIVersion: "rbac.authorization.k8s.io/v1", 40 | }, 41 | ObjectMeta: metav1.ObjectMeta{ 42 | Name: falco.Name, 43 | Namespace: falco.Namespace, 44 | Labels: falco.Labels, 45 | }, 46 | Rules: []rbacv1.PolicyRule{ 47 | { 48 | APIGroups: []string{""}, 49 | Resources: []string{"configmaps"}, 50 | Verbs: []string{"get", "list"}, 51 | }, 52 | { 53 | APIGroups: []string{artifactv1alpha1.GroupVersion.Group}, 54 | Resources: []string{"configs", "rulesfiles", "plugins"}, 55 | Verbs: []string{"get", "update", "list", "watch"}, 56 | }, 57 | }, 58 | } 59 | return role, nil 60 | }, 61 | generateOptions{ 62 | setControllerRef: true, 63 | isClusterScoped: false, 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /controllers/falco/roleBinding.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | rbacv1 "k8s.io/api/rbac/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 30 | ) 31 | 32 | // generateRoleBinding returns a RoleBinding for Falco. 33 | func generateRoleBinding(ctx context.Context, cl client.Client, falco *instancev1alpha1.Falco) (*unstructured.Unstructured, error) { 34 | return generateResourceFromFalcoInstance(ctx, cl, falco, 35 | func(falco *instancev1alpha1.Falco) (runtime.Object, error) { 36 | rb := &rbacv1.RoleBinding{ 37 | TypeMeta: metav1.TypeMeta{ 38 | Kind: "RoleBinding", 39 | APIVersion: "rbac.authorization.k8s.io/v1", 40 | }, 41 | ObjectMeta: metav1.ObjectMeta{ 42 | Namespace: falco.Namespace, 43 | Labels: falco.Labels, 44 | GenerateName: fmt.Sprintf("%s-", falco.Name), 45 | }, 46 | Subjects: []rbacv1.Subject{ 47 | { 48 | Kind: "ServiceAccount", 49 | Name: falco.Name, 50 | Namespace: falco.Namespace, 51 | }, 52 | }, 53 | RoleRef: rbacv1.RoleRef{ 54 | Kind: "Role", 55 | Name: falco.Name, 56 | APIGroup: "rbac.authorization.k8s.io", 57 | }, 58 | } 59 | return rb, nil 60 | }, 61 | generateOptions{ 62 | setControllerRef: true, 63 | isClusterScoped: false, 64 | }, 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /controllers/falco/roleBinding_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | rbacv1 "k8s.io/api/rbac/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 28 | 29 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 30 | ) 31 | 32 | func TestGenerateRoleBinding(t *testing.T) { 33 | // Create a test scheme. 34 | scheme := runtime.NewScheme() 35 | _ = rbacv1.AddToScheme(scheme) 36 | _ = instancev1alpha1.AddToScheme(scheme) 37 | 38 | tests := []struct { 39 | name string 40 | falco *instancev1alpha1.Falco 41 | wantErr bool 42 | }{ 43 | { 44 | name: "successfully generate role binding", 45 | falco: &instancev1alpha1.Falco{ 46 | ObjectMeta: metav1.ObjectMeta{ 47 | Name: "test-falco", 48 | Namespace: "default", 49 | Labels: map[string]string{ 50 | "app": "falco", 51 | }, 52 | }, 53 | }, 54 | wantErr: false, 55 | }, 56 | { 57 | name: "nil falco instance", 58 | falco: nil, 59 | wantErr: true, 60 | }, 61 | } 62 | 63 | for _, tt := range tests { 64 | t.Run(tt.name, func(t *testing.T) { 65 | // Create a fake client 66 | cl := fake.NewClientBuilder().WithScheme(scheme).Build() 67 | 68 | // Generate the role binding 69 | got, err := generateRoleBinding(context.Background(), cl, tt.falco) 70 | 71 | if tt.wantErr { 72 | assert.Error(t, err) 73 | return 74 | } 75 | 76 | assert.NoError(t, err) 77 | assert.NotNil(t, got) 78 | 79 | // Convert unstructured to RoleBinding 80 | roleBinding := &rbacv1.RoleBinding{} 81 | err = runtime.DefaultUnstructuredConverter.FromUnstructured(got.Object, roleBinding) 82 | assert.NoError(t, err) 83 | 84 | // Verify role binding properties 85 | assert.Equal(t, tt.falco.Name, roleBinding.Name) 86 | assert.Equal(t, tt.falco.Namespace, roleBinding.Namespace) 87 | assert.Equal(t, tt.falco.Labels, roleBinding.Labels) 88 | assert.Equal(t, "RoleBinding", roleBinding.Kind) 89 | assert.Equal(t, "rbac.authorization.k8s.io/v1", roleBinding.APIVersion) 90 | 91 | // Verify role ref 92 | assert.Equal(t, "Role", roleBinding.RoleRef.Kind) 93 | assert.Equal(t, "rbac.authorization.k8s.io", roleBinding.RoleRef.APIGroup) 94 | assert.Equal(t, tt.falco.Name, roleBinding.RoleRef.Name) 95 | 96 | // Verify subjects 97 | assert.Len(t, roleBinding.Subjects, 1) 98 | subject := roleBinding.Subjects[0] 99 | assert.Equal(t, "ServiceAccount", subject.Kind) 100 | assert.Equal(t, tt.falco.Name, subject.Name) 101 | assert.Equal(t, tt.falco.Namespace, subject.Namespace) 102 | }) 103 | } 104 | } 105 | 106 | func TestGenerateRoleBindingWithNilClient(t *testing.T) { 107 | falco := &instancev1alpha1.Falco{ 108 | ObjectMeta: metav1.ObjectMeta{ 109 | Name: "test-falco", 110 | Namespace: "default", 111 | }, 112 | } 113 | 114 | _, err := generateRoleBinding(context.Background(), nil, falco) 115 | assert.Error(t, err) 116 | } 117 | 118 | func TestGenerateRoleBindingCustomLabels(t *testing.T) { 119 | scheme := runtime.NewScheme() 120 | _ = rbacv1.AddToScheme(scheme) 121 | _ = instancev1alpha1.AddToScheme(scheme) 122 | cl := fake.NewClientBuilder().WithScheme(scheme).Build() 123 | 124 | customLabels := map[string]string{ 125 | "app": "falco", 126 | "environment": "test", 127 | "custom": "label", 128 | } 129 | 130 | falco := &instancev1alpha1.Falco{ 131 | ObjectMeta: metav1.ObjectMeta{ 132 | Name: "test-falco", 133 | Namespace: "default", 134 | Labels: customLabels, 135 | }, 136 | } 137 | 138 | got, err := generateRoleBinding(context.Background(), cl, falco) 139 | assert.NoError(t, err) 140 | assert.NotNil(t, got) 141 | 142 | roleBinding := &rbacv1.RoleBinding{} 143 | err = runtime.DefaultUnstructuredConverter.FromUnstructured(got.Object, roleBinding) 144 | assert.NoError(t, err) 145 | 146 | // Verify custom labels are properly set 147 | assert.Equal(t, customLabels, roleBinding.Labels) 148 | } 149 | -------------------------------------------------------------------------------- /controllers/falco/role_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | rbacv1 "k8s.io/api/rbac/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 28 | 29 | artifactv1alpha1 "github.com/falcosecurity/falco-operator/api/artifact/v1alpha1" 30 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 31 | ) 32 | 33 | func TestGenerateRole(t *testing.T) { 34 | // Create a test scheme 35 | scheme := runtime.NewScheme() 36 | _ = rbacv1.AddToScheme(scheme) 37 | _ = instancev1alpha1.AddToScheme(scheme) 38 | 39 | tests := []struct { 40 | name string 41 | falco *instancev1alpha1.Falco 42 | wantErr bool 43 | }{ 44 | { 45 | name: "successfully generate role", 46 | falco: &instancev1alpha1.Falco{ 47 | ObjectMeta: metav1.ObjectMeta{ 48 | Name: "test-falco", 49 | Namespace: "default", 50 | Labels: map[string]string{ 51 | "app": "falco", 52 | }, 53 | }, 54 | }, 55 | wantErr: false, 56 | }, 57 | } 58 | 59 | for _, tt := range tests { 60 | t.Run(tt.name, func(t *testing.T) { 61 | // Create a fake client 62 | cl := fake.NewClientBuilder().WithScheme(scheme).Build() 63 | 64 | // Generate the role 65 | got, err := generateRole(context.Background(), cl, tt.falco) 66 | 67 | if tt.wantErr { 68 | assert.Error(t, err) 69 | return 70 | } 71 | 72 | assert.NoError(t, err) 73 | assert.NotNil(t, got) 74 | 75 | // Convert unstructured to Role 76 | role := &rbacv1.Role{} 77 | err = runtime.DefaultUnstructuredConverter.FromUnstructured(got.Object, role) 78 | assert.NoError(t, err) 79 | 80 | // Verify role properties 81 | assert.Equal(t, tt.falco.Name, role.Name) 82 | assert.Equal(t, tt.falco.Namespace, role.Namespace) 83 | assert.Equal(t, tt.falco.Labels, role.Labels) 84 | assert.Equal(t, "Role", role.Kind) 85 | assert.Equal(t, "rbac.authorization.k8s.io/v1", role.APIVersion) 86 | 87 | // Verify rules 88 | assert.Len(t, role.Rules, 2) 89 | 90 | // Verify configmaps rule 91 | assert.Contains(t, role.Rules, rbacv1.PolicyRule{ 92 | APIGroups: []string{""}, 93 | Resources: []string{"configmaps"}, 94 | Verbs: []string{"get", "list"}, 95 | }) 96 | 97 | // Verify artifact rule 98 | assert.Contains(t, role.Rules, rbacv1.PolicyRule{ 99 | APIGroups: []string{artifactv1alpha1.GroupVersion.Group}, 100 | Resources: []string{"configs", "rulesfiles", "plugins"}, 101 | Verbs: []string{"get", "update", "list", "watch"}, 102 | }) 103 | }) 104 | } 105 | } 106 | 107 | func TestGenerateRoleWithNilFalco(t *testing.T) { 108 | scheme := runtime.NewScheme() 109 | _ = rbacv1.AddToScheme(scheme) 110 | cl := fake.NewClientBuilder().WithScheme(scheme).Build() 111 | 112 | _, err := generateRole(context.Background(), cl, nil) 113 | assert.Error(t, err) 114 | } 115 | 116 | func TestGenerateRoleWithNilClient(t *testing.T) { 117 | falco := &instancev1alpha1.Falco{ 118 | ObjectMeta: metav1.ObjectMeta{ 119 | Name: "test-falco", 120 | Namespace: "default", 121 | }, 122 | } 123 | 124 | _, err := generateRole(context.Background(), nil, falco) 125 | assert.Error(t, err) 126 | } 127 | -------------------------------------------------------------------------------- /controllers/falco/service.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/apimachinery/pkg/util/intstr" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 30 | ) 31 | 32 | // generateService returns a service for Falco. 33 | func generateService(ctx context.Context, cl client.Client, falco *instancev1alpha1.Falco) (*unstructured.Unstructured, error) { 34 | return generateResourceFromFalcoInstance(ctx, cl, falco, 35 | func(falco *instancev1alpha1.Falco) (runtime.Object, error) { 36 | svc := &corev1.Service{ 37 | TypeMeta: metav1.TypeMeta{ 38 | Kind: "Service", 39 | APIVersion: "v1", 40 | }, 41 | ObjectMeta: metav1.ObjectMeta{ 42 | Name: falco.Name, 43 | Namespace: falco.Namespace, 44 | Labels: falco.Labels, 45 | }, 46 | Spec: corev1.ServiceSpec{ 47 | Type: corev1.ServiceTypeClusterIP, 48 | Ports: []corev1.ServicePort{ 49 | { 50 | Name: "web", 51 | Protocol: corev1.ProtocolTCP, 52 | Port: 8765, 53 | TargetPort: intstr.FromInt32(8765), 54 | }, 55 | }, 56 | Selector: map[string]string{ 57 | "app.kubernetes.io/name": falco.Name, 58 | "app.kubernetes.io/instance": falco.Name, 59 | }, 60 | }, 61 | } 62 | 63 | return svc, nil 64 | }, 65 | generateOptions{ 66 | setControllerRef: true, 67 | isClusterScoped: false, 68 | }, 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /controllers/falco/serviceAccount.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | 22 | corev1 "k8s.io/api/core/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 29 | ) 30 | 31 | // generateServiceAccount returns a ServiceAccount for Falco. 32 | func generateServiceAccount(ctx context.Context, cl client.Client, falco *instancev1alpha1.Falco) (*unstructured.Unstructured, error) { 33 | return generateResourceFromFalcoInstance(ctx, cl, falco, 34 | func(falco *instancev1alpha1.Falco) (runtime.Object, error) { 35 | sa := &corev1.ServiceAccount{ 36 | TypeMeta: metav1.TypeMeta{ 37 | Kind: "ServiceAccount", 38 | APIVersion: "v1", 39 | }, 40 | ObjectMeta: metav1.ObjectMeta{ 41 | Name: falco.Name, 42 | Namespace: falco.Namespace, 43 | Labels: falco.Labels, 44 | }, 45 | } 46 | return sa, nil 47 | }, 48 | generateOptions{ 49 | setControllerRef: true, 50 | isClusterScoped: false, 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /controllers/falco/serviceAccount_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 28 | 29 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 30 | ) 31 | 32 | func TestGenerateServiceAccount(t *testing.T) { 33 | // Create a test scheme. 34 | scheme := runtime.NewScheme() 35 | _ = corev1.AddToScheme(scheme) 36 | _ = instancev1alpha1.AddToScheme(scheme) 37 | 38 | tests := []struct { 39 | name string 40 | falco *instancev1alpha1.Falco 41 | wantErr bool 42 | expectedLabels map[string]string 43 | }{ 44 | { 45 | name: "successful service account generation", 46 | falco: &instancev1alpha1.Falco{ 47 | ObjectMeta: metav1.ObjectMeta{ 48 | Name: "test-falco", 49 | Namespace: "default", 50 | Labels: map[string]string{ 51 | "app": "falco", 52 | }, 53 | }, 54 | }, 55 | wantErr: false, 56 | expectedLabels: map[string]string{ 57 | "app": "falco", 58 | }, 59 | }, 60 | { 61 | name: "service account with custom labels", 62 | falco: &instancev1alpha1.Falco{ 63 | ObjectMeta: metav1.ObjectMeta{ 64 | Name: "test-falco", 65 | Namespace: "custom-namespace", 66 | Labels: map[string]string{ 67 | "app": "falco", 68 | "environment": "test", 69 | "custom": "label", 70 | }, 71 | }, 72 | }, 73 | wantErr: false, 74 | expectedLabels: map[string]string{ 75 | "app": "falco", 76 | "environment": "test", 77 | "custom": "label", 78 | }, 79 | }, 80 | { 81 | name: "nil falco instance", 82 | falco: nil, 83 | wantErr: true, 84 | }, 85 | } 86 | 87 | for _, tt := range tests { 88 | t.Run(tt.name, func(t *testing.T) { 89 | cl := fake.NewClientBuilder().WithScheme(scheme).Build() 90 | got, err := generateServiceAccount(context.Background(), cl, tt.falco) 91 | 92 | if tt.wantErr { 93 | assert.Error(t, err) 94 | return 95 | } 96 | 97 | assert.NoError(t, err) 98 | assert.NotNil(t, got) 99 | 100 | // Convert unstructured to ServiceAccount. 101 | sa := &corev1.ServiceAccount{} 102 | err = runtime.DefaultUnstructuredConverter.FromUnstructured(got.Object, sa) 103 | assert.NoError(t, err) 104 | 105 | // Verify service account properties. 106 | assert.Equal(t, tt.falco.Name, sa.Name) 107 | assert.Equal(t, tt.falco.Namespace, sa.Namespace) 108 | assert.Equal(t, tt.falco.Labels, sa.Labels) 109 | assert.Equal(t, "ServiceAccount", sa.Kind) 110 | assert.Equal(t, "v1", sa.APIVersion) 111 | 112 | // Verify labels if expected. 113 | if tt.expectedLabels != nil { 114 | assert.Equal(t, tt.expectedLabels, sa.Labels) 115 | } 116 | }) 117 | } 118 | } 119 | 120 | func TestGenerateServiceAccountWithNilClient(t *testing.T) { 121 | falco := &instancev1alpha1.Falco{ 122 | ObjectMeta: metav1.ObjectMeta{ 123 | Name: "test-falco", 124 | Namespace: "default", 125 | }, 126 | } 127 | 128 | _, err := generateServiceAccount(context.Background(), nil, falco) 129 | assert.Error(t, err) 130 | } 131 | -------------------------------------------------------------------------------- /controllers/falco/service_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "k8s.io/apimachinery/pkg/util/intstr" 28 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 29 | 30 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 31 | ) 32 | 33 | func TestGenerateService(t *testing.T) { 34 | // Create a test scheme. 35 | scheme := runtime.NewScheme() 36 | _ = corev1.AddToScheme(scheme) 37 | _ = instancev1alpha1.AddToScheme(scheme) 38 | 39 | tests := []struct { 40 | name string 41 | falco *instancev1alpha1.Falco 42 | wantErr bool 43 | expectedPorts []corev1.ServicePort 44 | expectedLabels map[string]string 45 | }{ 46 | { 47 | name: "successful service generation with defaults", 48 | falco: &instancev1alpha1.Falco{ 49 | ObjectMeta: metav1.ObjectMeta{ 50 | Name: "test-falco", 51 | Namespace: "default", 52 | Labels: map[string]string{ 53 | "app": "falco", 54 | }, 55 | }, 56 | }, 57 | wantErr: false, 58 | expectedPorts: []corev1.ServicePort{ 59 | { 60 | Name: "web", 61 | Protocol: corev1.ProtocolTCP, 62 | Port: 8765, 63 | TargetPort: intstr.FromInt32(8765), 64 | }, 65 | }, 66 | expectedLabels: map[string]string{ 67 | "app": "falco", 68 | }, 69 | }, 70 | { 71 | name: "service with custom labels", 72 | falco: &instancev1alpha1.Falco{ 73 | ObjectMeta: metav1.ObjectMeta{ 74 | Name: "test-falco", 75 | Namespace: "default", 76 | Labels: map[string]string{ 77 | "app": "falco", 78 | "environment": "test", 79 | "custom": "label", 80 | }, 81 | }, 82 | }, 83 | wantErr: false, 84 | expectedLabels: map[string]string{ 85 | "app": "falco", 86 | "environment": "test", 87 | "custom": "label", 88 | }, 89 | }, 90 | { 91 | name: "nil falco instance", 92 | falco: nil, 93 | wantErr: true, 94 | }, 95 | } 96 | 97 | for _, tt := range tests { 98 | t.Run(tt.name, func(t *testing.T) { 99 | cl := fake.NewClientBuilder().WithScheme(scheme).Build() 100 | got, err := generateService(context.Background(), cl, tt.falco) 101 | 102 | if tt.wantErr { 103 | assert.Error(t, err) 104 | return 105 | } 106 | 107 | assert.NoError(t, err) 108 | assert.NotNil(t, got) 109 | 110 | // Convert unstructured to Service. 111 | service := &corev1.Service{} 112 | err = runtime.DefaultUnstructuredConverter.FromUnstructured(got.Object, service) 113 | assert.NoError(t, err) 114 | 115 | // Verify service properties. 116 | assert.Equal(t, tt.falco.Name, service.Name) 117 | assert.Equal(t, tt.falco.Namespace, service.Namespace) 118 | assert.Equal(t, tt.falco.Labels, service.Labels) 119 | assert.Equal(t, "Service", service.Kind) 120 | assert.Equal(t, "v1", service.APIVersion) 121 | 122 | // Verify service type is ClusterIP by default. 123 | assert.Equal(t, corev1.ServiceTypeClusterIP, service.Spec.Type) 124 | 125 | // Verify selector. 126 | assert.Equal(t, map[string]string{ 127 | "app.kubernetes.io/name": tt.falco.Name, 128 | "app.kubernetes.io/instance": tt.falco.Name, 129 | }, service.Spec.Selector) 130 | 131 | // Verify ports if expected. 132 | if tt.expectedPorts != nil { 133 | assert.Equal(t, tt.expectedPorts, service.Spec.Ports) 134 | } 135 | 136 | // Verify labels if expected. 137 | if tt.expectedLabels != nil { 138 | assert.Equal(t, tt.expectedLabels, service.Labels) 139 | } 140 | }) 141 | } 142 | } 143 | 144 | func TestGenerateServiceWithNilClient(t *testing.T) { 145 | falco := &instancev1alpha1.Falco{ 146 | ObjectMeta: metav1.ObjectMeta{ 147 | Name: "test-falco", 148 | Namespace: "default", 149 | }, 150 | } 151 | 152 | _, err := generateService(context.Background(), nil, falco) 153 | assert.Error(t, err) 154 | } 155 | -------------------------------------------------------------------------------- /controllers/falco/suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package falco 18 | 19 | import ( 20 | "context" 21 | "os" 22 | "path/filepath" 23 | "testing" 24 | 25 | . "github.com/onsi/ginkgo/v2" 26 | . "github.com/onsi/gomega" 27 | "k8s.io/client-go/kubernetes/scheme" 28 | "k8s.io/client-go/rest" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | "sigs.k8s.io/controller-runtime/pkg/envtest" 31 | logf "sigs.k8s.io/controller-runtime/pkg/log" 32 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 33 | 34 | instancev1alpha1 "github.com/falcosecurity/falco-operator/api/instance/v1alpha1" 35 | ) 36 | 37 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 38 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 39 | 40 | var ( 41 | ctx context.Context 42 | cancel context.CancelFunc 43 | testEnv *envtest.Environment 44 | cfg *rest.Config 45 | k8sClient client.Client 46 | ) 47 | 48 | func TestControllers(t *testing.T) { 49 | RegisterFailHandler(Fail) 50 | 51 | RunSpecs(t, "Controller Suite") 52 | } 53 | 54 | var _ = BeforeSuite(func() { 55 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 56 | 57 | ctx, cancel = context.WithCancel(context.TODO()) 58 | 59 | var err error 60 | err = instancev1alpha1.AddToScheme(scheme.Scheme) 61 | Expect(err).NotTo(HaveOccurred()) 62 | 63 | // +kubebuilder:scaffold:scheme 64 | 65 | By("bootstrapping test environment") 66 | testEnv = &envtest.Environment{ 67 | CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, 68 | ErrorIfCRDPathMissing: true, 69 | } 70 | 71 | // Retrieve the first found binary directory to allow running tests from IDEs 72 | if getFirstFoundEnvTestBinaryDir() != "" { 73 | testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir() 74 | } 75 | 76 | // cfg is defined in this file globally. 77 | cfg, err = testEnv.Start() 78 | Expect(err).NotTo(HaveOccurred()) 79 | Expect(cfg).NotTo(BeNil()) 80 | 81 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 82 | Expect(err).NotTo(HaveOccurred()) 83 | Expect(k8sClient).NotTo(BeNil()) 84 | }) 85 | 86 | var _ = AfterSuite(func() { 87 | By("tearing down the test environment") 88 | cancel() 89 | err := testEnv.Stop() 90 | Expect(err).NotTo(HaveOccurred()) 91 | }) 92 | 93 | // getFirstFoundEnvTestBinaryDir locates the first binary in the specified path. 94 | // ENVTEST-based tests depend on specific binaries, usually located in paths set by 95 | // controller-runtime. When running tests directly (e.g., via an IDE) without using 96 | // Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured. 97 | // 98 | // This function streamlines the process by finding the required binaries, similar to 99 | // setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are 100 | // properly set up, run 'make setup-envtest' beforehand. 101 | func getFirstFoundEnvTestBinaryDir() string { 102 | basePath := filepath.Join("..", "..", "..", "bin", "k8s") 103 | entries, err := os.ReadDir(basePath) 104 | if err != nil { 105 | logf.Log.Error(err, "Failed to read directory", "path", basePath) 106 | return "" 107 | } 108 | for _, entry := range entries { 109 | if entry.IsDir() { 110 | return filepath.Join(basePath, entry.Name()) 111 | } 112 | } 113 | return "" 114 | } 115 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/falcosecurity/falco-operator 2 | 3 | go 1.24.0 4 | 5 | godebug default=go1.23 6 | 7 | require ( 8 | github.com/onsi/ginkgo/v2 v2.23.4 9 | github.com/onsi/gomega v1.37.0 10 | github.com/opencontainers/image-spec v1.1.1 11 | github.com/stretchr/testify v1.10.0 12 | gopkg.in/yaml.v3 v3.0.1 13 | k8s.io/api v0.33.1 14 | k8s.io/apimachinery v0.33.1 15 | k8s.io/client-go v0.33.1 16 | k8s.io/utils v0.0.0-20241210054802-24370beab758 17 | oras.land/oras-go/v2 v2.6.0 18 | sigs.k8s.io/controller-runtime v0.21.0 19 | sigs.k8s.io/structured-merge-diff/v4 v4.7.0 20 | ) 21 | 22 | require ( 23 | cel.dev/expr v0.19.2 // indirect 24 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 25 | github.com/beorn7/perks v1.0.1 // indirect 26 | github.com/blang/semver/v4 v4.0.0 // indirect 27 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 28 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 29 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 30 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 31 | github.com/evanphx/json-patch v5.9.0+incompatible // indirect 32 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 33 | github.com/felixge/httpsnoop v1.0.4 // indirect 34 | github.com/fsnotify/fsnotify v1.8.0 // indirect 35 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 36 | github.com/go-logr/logr v1.4.2 // indirect 37 | github.com/go-logr/stdr v1.2.2 // indirect 38 | github.com/go-logr/zapr v1.3.0 // indirect 39 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 40 | github.com/go-openapi/jsonreference v0.21.0 // indirect 41 | github.com/go-openapi/swag v0.23.0 // indirect 42 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 43 | github.com/gogo/protobuf v1.3.2 // indirect 44 | github.com/google/btree v1.1.3 // indirect 45 | github.com/google/cel-go v0.23.2 // indirect 46 | github.com/google/gnostic-models v0.6.9 // indirect 47 | github.com/google/go-cmp v0.7.0 // indirect 48 | github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect 49 | github.com/google/uuid v1.6.0 // indirect 50 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect 51 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 52 | github.com/josharian/intern v1.0.0 // indirect 53 | github.com/json-iterator/go v1.1.12 // indirect 54 | github.com/mailru/easyjson v0.9.0 // indirect 55 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 56 | github.com/modern-go/reflect2 v1.0.2 // indirect 57 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 58 | github.com/opencontainers/go-digest v1.0.0 // indirect 59 | github.com/pkg/errors v0.9.1 // indirect 60 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 61 | github.com/prometheus/client_golang v1.22.0 // indirect 62 | github.com/prometheus/client_model v0.6.1 // indirect 63 | github.com/prometheus/common v0.62.0 // indirect 64 | github.com/prometheus/procfs v0.15.1 // indirect 65 | github.com/spf13/cobra v1.9.1 // indirect 66 | github.com/spf13/pflag v1.0.6 // indirect 67 | github.com/stoewer/go-strcase v1.3.0 // indirect 68 | github.com/x448/float16 v0.8.4 // indirect 69 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 70 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect 71 | go.opentelemetry.io/otel v1.34.0 // indirect 72 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect 73 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect 74 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 75 | go.opentelemetry.io/otel/sdk v1.34.0 // indirect 76 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 77 | go.opentelemetry.io/proto/otlp v1.5.0 // indirect 78 | go.uber.org/automaxprocs v1.6.0 // indirect 79 | go.uber.org/multierr v1.11.0 // indirect 80 | go.uber.org/zap v1.27.0 // indirect 81 | golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect 82 | golang.org/x/net v0.38.0 // indirect 83 | golang.org/x/oauth2 v0.28.0 // indirect 84 | golang.org/x/sync v0.14.0 // indirect 85 | golang.org/x/sys v0.32.0 // indirect 86 | golang.org/x/term v0.30.0 // indirect 87 | golang.org/x/text v0.23.0 // indirect 88 | golang.org/x/time v0.11.0 // indirect 89 | golang.org/x/tools v0.31.0 // indirect 90 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 91 | google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect 92 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect 93 | google.golang.org/grpc v1.71.0 // indirect 94 | google.golang.org/protobuf v1.36.5 // indirect 95 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 96 | gopkg.in/inf.v0 v0.9.1 // indirect 97 | k8s.io/apiextensions-apiserver v0.33.0 // indirect 98 | k8s.io/apiserver v0.33.0 // indirect 99 | k8s.io/component-base v0.33.0 // indirect 100 | k8s.io/klog/v2 v2.130.1 // indirect 101 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 102 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect 103 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 104 | sigs.k8s.io/randfill v1.0.0 // indirect 105 | sigs.k8s.io/yaml v1.4.0 // indirect 106 | ) 107 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controller defines controllers' logic. -------------------------------------------------------------------------------- /hack/update-kube-static-scheme-parser.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This file is a modified version of `update_static_schema.sh` from the ArgoProj GitOps Engine project. 4 | # Original source: https://github.com/argoproj/gitops-engine/blob/master/hack/update_static_schema.sh 5 | # License: Apache 2.0 (see LICENSE file in the source repository) 6 | 7 | set -euox pipefail 8 | 9 | # Get the k8s library version from go.mod, stripping the trailing newline 10 | k8s_lib_version=$(grep "k8s.io/client-go" go.mod | awk '{print $2}' | head -n 1 | tr -d '\n') 11 | 12 | # Download the parser file from the k8s library 13 | curl -sL "https://raw.githubusercontent.com/kubernetes/client-go/$k8s_lib_version/applyconfigurations/internal/internal.go" -o internal/pkg/scheme/parser.go 14 | 15 | # Add a line to the beginning of the file saying that this is the script that generated it. 16 | sed -i '1s|^|// Code generated by hack/update-kube-static-scheme-parser.sh; DO NOT EDIT.\n// Everything below is downloaded from applyconfigurations/internal/internal.go in kubernetes/client-go.\n\n|' internal/pkg/scheme/parser.go 17 | 18 | # Replace "package internal" with "package scheme" in the parser file 19 | sed -i 's/package\ internal/package\ scheme/' internal/pkg/scheme/parser.go 20 | # Replace "func Parser" with "func StaticParser" 21 | sed -i 's/package\\ internal/package\\ scheme/' internal/pkg/scheme/parser.go 22 | -------------------------------------------------------------------------------- /internal/pkg/artifact/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package artifact provides the logic to store artifacts to the local filesystem. 18 | // It manages their lifecycle based on the CR instances. 19 | package artifact 20 | -------------------------------------------------------------------------------- /internal/pkg/common/archive.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package common 18 | 19 | import ( 20 | "archive/tar" 21 | "compress/gzip" 22 | "context" 23 | "errors" 24 | "fmt" 25 | "io" 26 | "os" 27 | "path/filepath" 28 | "strings" 29 | ) 30 | 31 | type link struct { 32 | Name string 33 | Path string 34 | } 35 | 36 | // ExtractTarGz extracts a *.tar.gz compressed archive and moves its content to destDir. 37 | // Returns a slice containing the full path of the extracted files. 38 | func ExtractTarGz(ctx context.Context, gzipStream io.Reader, destDir string, stripPathComponents int) ([]string, error) { 39 | var ( 40 | files []string 41 | links []link 42 | symlinks []link 43 | err error 44 | ) 45 | 46 | // We need an absolute path 47 | destDir, err = filepath.Abs(destDir) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | uncompressedStream, err := gzip.NewReader(gzipStream) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | tarReader := tar.NewReader(uncompressedStream) 58 | for { 59 | select { 60 | case <-ctx.Done(): 61 | return nil, errors.New("interrupted") 62 | default: 63 | } 64 | 65 | header, err := tarReader.Next() 66 | if errors.Is(err, io.EOF) { 67 | break 68 | } 69 | if err != nil { 70 | return nil, err 71 | } 72 | if strings.Contains(header.Name, "..") { 73 | return nil, fmt.Errorf("not allowed relative path in tar archive") 74 | } 75 | 76 | path := header.Name 77 | if stripPathComponents > 0 { 78 | path = stripComponents(path, stripPathComponents) 79 | } 80 | if path == "" { 81 | continue 82 | } 83 | 84 | if path, err = safeConcat(destDir, filepath.Clean(path)); err != nil { 85 | // Skip paths that would escape destDir 86 | continue 87 | } 88 | info := header.FileInfo() 89 | 90 | switch header.Typeflag { 91 | case tar.TypeDir: 92 | if err := os.MkdirAll(path, info.Mode()); err != nil { 93 | return nil, err 94 | } 95 | case tar.TypeReg: 96 | outFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode()) 97 | if err != nil { 98 | return nil, err 99 | } 100 | if written, err := io.CopyN(outFile, tarReader, header.Size); err != nil { 101 | return nil, err 102 | } else if written != header.Size { 103 | return nil, io.ErrShortWrite 104 | } 105 | if err := outFile.Close(); err != nil { 106 | return nil, err 107 | } 108 | files = append(files, path) 109 | case tar.TypeLink: 110 | name := header.Linkname 111 | if stripPathComponents > 0 { 112 | name = stripComponents(name, stripPathComponents) 113 | } 114 | if name == "" { 115 | continue 116 | } 117 | 118 | name = filepath.Join(destDir, filepath.Clean(name)) 119 | links = append(links, link{Path: path, Name: name}) 120 | case tar.TypeSymlink: 121 | symlinks = append(symlinks, link{Path: path, Name: header.Linkname}) 122 | default: 123 | return nil, fmt.Errorf("extractTarGz: uknown type: %b in %s", header.Typeflag, header.Name) 124 | } 125 | } 126 | 127 | // Now we make another pass creating the links 128 | for i := range links { 129 | select { 130 | case <-ctx.Done(): 131 | return nil, errors.New("interrupted") 132 | default: 133 | } 134 | if err := os.Link(links[i].Name, links[i].Path); err != nil { 135 | return nil, err 136 | } 137 | } 138 | 139 | for i := range symlinks { 140 | select { 141 | case <-ctx.Done(): 142 | return nil, errors.New("interrupted") 143 | default: 144 | } 145 | if err := os.Symlink(symlinks[i].Name, symlinks[i].Path); err != nil { 146 | return nil, err 147 | } 148 | } 149 | return files, nil 150 | } 151 | 152 | func stripComponents(headerName string, stripComponents int) string { 153 | if stripComponents == 0 { 154 | return headerName 155 | } 156 | names := strings.Split(headerName, string(filepath.Separator)) 157 | if len(names) < stripComponents { 158 | return headerName 159 | } 160 | return filepath.Clean(strings.Join(names[stripComponents:], string(filepath.Separator))) 161 | } 162 | 163 | // safeConcat concatenates destDir and name 164 | // but returns an error if the resulting path points outside 'destDir'. 165 | func safeConcat(destDir, name string) (string, error) { 166 | res := filepath.Join(destDir, name) 167 | if !strings.HasSuffix(destDir, string(os.PathSeparator)) { 168 | destDir += string(os.PathSeparator) 169 | } 170 | if !strings.HasPrefix(res, destDir) { 171 | return res, fmt.Errorf("unsafe path concatenation: '%s' with '%s'", destDir, name) 172 | } 173 | return res, nil 174 | } 175 | -------------------------------------------------------------------------------- /internal/pkg/common/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package common provides common utilities for the application. 18 | package common 19 | -------------------------------------------------------------------------------- /internal/pkg/common/finalizer.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package common 18 | 19 | import "fmt" 20 | 21 | // FormatFinalizerName creates a finalizer name by combining a prefix and suffix with a hyphen. 22 | func FormatFinalizerName(prefix, suffix string) string { 23 | return fmt.Sprintf("%s-%s", prefix, suffix) 24 | } 25 | -------------------------------------------------------------------------------- /internal/pkg/common/sidecar.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package common 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "strings" 23 | 24 | "k8s.io/client-go/kubernetes" 25 | "k8s.io/client-go/rest" 26 | ) 27 | 28 | // IsSidecarContainersFeatureEnabled checks if the SidecarContainers feature is enabled in the cluster. 29 | // It returns true if the feature is enabled, false otherwise. 30 | func IsSidecarContainersFeatureEnabled(cfg *rest.Config) (bool, error) { 31 | clSet, err := kubernetes.NewForConfig(cfg) 32 | if err != nil { 33 | return false, fmt.Errorf("unable to create a client set: %w", err) 34 | } 35 | 36 | req := clSet.RESTClient().Get().AbsPath("/metrics").RequestURI("/metrics").Do(context.Background()) 37 | if req.Error() != nil { 38 | return false, fmt.Errorf("unable to get metrics: %w", req.Error()) 39 | } 40 | 41 | rawMetrics, err := req.Raw() 42 | if err != nil { 43 | return false, fmt.Errorf("unable to get metrics: %w", err) 44 | } 45 | 46 | if strings.Contains(string(rawMetrics), "kubernetes_feature_enabled") && strings.Contains(string(rawMetrics), "SidecarContainers") { 47 | return true, nil 48 | } 49 | return false, nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/pkg/controllerhelper/deletion.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package controllerhelper contains common helper for controllers. 18 | package controllerhelper 19 | 20 | import ( 21 | "context" 22 | 23 | apierrors "k8s.io/apimachinery/pkg/api/errors" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 26 | "sigs.k8s.io/controller-runtime/pkg/log" 27 | 28 | "github.com/falcosecurity/falco-operator/internal/pkg/artifact" 29 | ) 30 | 31 | // HandleObjectDeletion handles the deletion of an object. 32 | // It removes the finalizer and cleans up the local resources associated with the object. 33 | func HandleObjectDeletion(ctx context.Context, cl client.Client, am *artifact.Manager, finalizer string, obj client.Object) (bool, error) { 34 | logger := log.FromContext(ctx) 35 | 36 | if !obj.GetDeletionTimestamp().IsZero() { 37 | if controllerutil.ContainsFinalizer(obj, finalizer) { 38 | logger.Info("Config instance marked for deletion, cleaning up") 39 | if err := am.RemoveAll(ctx, obj.GetName()); err != nil { 40 | return false, err 41 | } 42 | 43 | // Remove the finalizer. 44 | controllerutil.RemoveFinalizer(obj, finalizer) 45 | if err := cl.Update(ctx, obj); err != nil && !apierrors.IsConflict(err) { 46 | logger.Error(err, "unable to remove finalizer", "finalizer", finalizer) 47 | return false, err 48 | } else if apierrors.IsConflict(err) { 49 | logger.Info("Conflict while removing finalizer, retrying") 50 | // It has already been added to the queue, so we return nil. 51 | return true, nil 52 | } 53 | } 54 | return true, nil 55 | } 56 | return false, nil 57 | } 58 | 59 | // RemoveLocalResources removes local resources associated with the object. 60 | // Helps to clean up local resources when the object is not targeting the current node anymore. 61 | // It also removes the finalizer from the object. 62 | func RemoveLocalResources(ctx context.Context, cl client.Client, am *artifact.Manager, finalizer string, obj client.Object) (bool, error) { 63 | // If the object contains the finalizer it means that we processed it before. 64 | if controllerutil.ContainsFinalizer(obj, finalizer) { 65 | if err := am.RemoveAll(ctx, obj.GetName()); err != nil { 66 | return false, err 67 | } 68 | 69 | // Remove the finalizer. 70 | controllerutil.RemoveFinalizer(obj, finalizer) 71 | if err := cl.Update(ctx, obj); err != nil && !apierrors.IsConflict(err) { 72 | return false, err 73 | } else if apierrors.IsConflict(err) { 74 | // It has already been added to the queue, so we return nil. 75 | return true, nil 76 | } 77 | } 78 | return true, nil 79 | } 80 | -------------------------------------------------------------------------------- /internal/pkg/controllerhelper/node.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package controllerhelper 18 | 19 | import ( 20 | "context" 21 | 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/labels" 24 | "sigs.k8s.io/controller-runtime/pkg/client" 25 | "sigs.k8s.io/controller-runtime/pkg/log" 26 | ) 27 | 28 | // NodeMatchesSelector checks if a selector matches the node labels. 29 | func NodeMatchesSelector(ctx context.Context, cl client.Client, nodeName string, labelSelector *metav1.LabelSelector) (bool, error) { 30 | logger := log.FromContext(ctx) 31 | 32 | // If the labelSelector is nil, return true. 33 | if labelSelector == nil { 34 | logger.V(2).Info("LabelSelector is nil, returning true") 35 | return true, nil 36 | } 37 | 38 | // Fetch the partial object metadata for the node. 39 | node := &metav1.PartialObjectMetadata{ 40 | TypeMeta: metav1.TypeMeta{ 41 | Kind: "Node", 42 | APIVersion: "v1", 43 | }, 44 | } 45 | logger.V(2).Info("Fetching node", "name", nodeName) 46 | if err := cl.Get(ctx, client.ObjectKey{Name: nodeName}, node); err != nil { 47 | logger.Error(err, "unable to fetch node") 48 | return false, err 49 | } 50 | 51 | // Convert the LabelSelector to a Selector. 52 | selector, err := metav1.LabelSelectorAsSelector(labelSelector) 53 | if err != nil { 54 | logger.Error(err, "invalid label selector", "labelSelector", labelSelector) 55 | return false, err 56 | } 57 | 58 | // Check if the node matches the selector. 59 | logger.V(2).Info("Checking node labelSelector", "node", nodeName, "labelSelector", labelSelector) 60 | if selector.Matches(labels.Set(node.Labels)) { 61 | logger.V(2).Info("Node matches labelSelector", "node", nodeName) 62 | return true, nil 63 | } else { 64 | logger.V(2).Info("Node does not match labelSelector", "node", nodeName) 65 | return false, nil 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /internal/pkg/controllerhelper/node_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package controllerhelper_test 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | corev1 "k8s.io/api/core/v1" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 29 | 30 | "github.com/falcosecurity/falco-operator/internal/pkg/controllerhelper" 31 | ) 32 | 33 | func TestNodeMatchesSelector(t *testing.T) { 34 | scheme := runtime.NewScheme() 35 | _ = corev1.AddToScheme(scheme) 36 | 37 | tests := []struct { 38 | name string 39 | nodeName string 40 | nodeLabels map[string]string 41 | labelSelector *metav1.LabelSelector 42 | expectedMatch bool 43 | expectedError bool 44 | }{ 45 | { 46 | name: "Node matches labelSelector", 47 | nodeName: "node1", 48 | nodeLabels: map[string]string{ 49 | "key1": "value1", 50 | "key2": "value2", 51 | }, 52 | labelSelector: &metav1.LabelSelector{ 53 | MatchLabels: map[string]string{ 54 | "key1": "value1", 55 | }, 56 | }, 57 | expectedMatch: true, 58 | expectedError: false, 59 | }, 60 | { 61 | name: "Node does not match labelSelector", 62 | nodeName: "node2", 63 | nodeLabels: map[string]string{ 64 | "key1": "value1", 65 | }, 66 | labelSelector: &metav1.LabelSelector{ 67 | MatchLabels: map[string]string{ 68 | "key2": "value2", 69 | }, 70 | }, 71 | expectedMatch: false, 72 | expectedError: false, 73 | }, 74 | { 75 | name: "Invalid labelSelector", 76 | nodeName: "node3", 77 | nodeLabels: map[string]string{ 78 | "key1": "value1", 79 | }, 80 | labelSelector: &metav1.LabelSelector{ 81 | MatchExpressions: []metav1.LabelSelectorRequirement{ 82 | { 83 | Key: "key1", 84 | Operator: "InvalidOperator", 85 | }, 86 | }, 87 | }, 88 | expectedMatch: false, 89 | expectedError: true, 90 | }, 91 | { 92 | name: "Nil labelSelector (matches all nodes)", 93 | nodeName: "node4", 94 | nodeLabels: map[string]string{}, 95 | labelSelector: nil, 96 | expectedMatch: true, 97 | expectedError: false, 98 | }, 99 | { 100 | name: "Node not found", 101 | nodeName: "nonexistent-node", 102 | nodeLabels: map[string]string{}, 103 | labelSelector: &metav1.LabelSelector{ 104 | MatchLabels: map[string]string{ 105 | "key1": "value1", 106 | }, 107 | }, 108 | expectedMatch: false, 109 | expectedError: true, 110 | }, 111 | } 112 | 113 | for _, tt := range tests { 114 | t.Run(tt.name, func(t *testing.T) { 115 | var fakeClient client.Client 116 | if tt.name != "Node not found" { // Only add the node if it exists 117 | node := &corev1.Node{ 118 | ObjectMeta: metav1.ObjectMeta{ 119 | Name: tt.nodeName, 120 | Labels: tt.nodeLabels, 121 | }, 122 | } 123 | fakeClient = fake.NewClientBuilder().WithScheme(scheme).WithObjects(node).Build() 124 | } else { 125 | // Create a fake client without any objects, to simulate a node not found. 126 | fakeClient = fake.NewClientBuilder().WithScheme(scheme).Build() 127 | } 128 | 129 | match, err := controllerhelper.NodeMatchesSelector(context.TODO(), fakeClient, tt.nodeName, tt.labelSelector) 130 | 131 | if tt.expectedError { 132 | assert.Error(t, err) 133 | } else { 134 | assert.NoError(t, err) 135 | } 136 | assert.Equal(t, tt.expectedMatch, match) 137 | }) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /internal/pkg/credentials/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package credentials contains the logic to manage credentials 18 | // for accessing remote registries. 19 | package credentials 20 | -------------------------------------------------------------------------------- /internal/pkg/credentials/secret.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package credentials 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | corev1 "k8s.io/api/core/v1" 24 | "k8s.io/apimachinery/pkg/types" 25 | "oras.land/oras-go/v2/registry/remote/auth" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | commonv1alpha1 "github.com/falcosecurity/falco-operator/api/common/v1alpha1" 29 | ) 30 | 31 | func credentialFuncFromCredentials(creds auth.Credential) auth.CredentialFunc { 32 | return func(ctx context.Context, hostport string) (auth.Credential, error) { 33 | return creds, nil 34 | } 35 | } 36 | 37 | // GetCredentialsFromSecret retrieves credentials from the Kubernetes secret 38 | // referenced by the OCIPullSecret and returns an ORAS credential. 39 | func GetCredentialsFromSecret(ctx context.Context, k8sClient client.Client, namespace string, pullSecret *commonv1alpha1.OCIPullSecret) (auth.CredentialFunc, error) { 40 | var creds = auth.EmptyCredential 41 | 42 | if pullSecret == nil { 43 | return credentialFuncFromCredentials(creds), nil 44 | } 45 | 46 | // Fetch the secret 47 | secret := &corev1.Secret{} 48 | secretNamespacedName := types.NamespacedName{ 49 | Name: pullSecret.SecretName, 50 | Namespace: namespace, 51 | } 52 | 53 | if err := k8sClient.Get(ctx, secretNamespacedName, secret); err != nil { 54 | return nil, fmt.Errorf("failed to get pull secret %s: %w", pullSecret.SecretName, err) 55 | } 56 | 57 | // Set default keys if not provided 58 | usernameKey := pullSecret.UsernameKey 59 | // This should never happen, but it's better to be safe than sorry. 60 | if usernameKey == "" { 61 | usernameKey = "username" 62 | } 63 | 64 | passwordKey := pullSecret.PasswordKey 65 | // This should never happen, but it's better to be safe than sorry. 66 | if passwordKey == "" { 67 | passwordKey = "password" 68 | } 69 | 70 | // Extract username and password 71 | username, ok := secret.Data[usernameKey] 72 | if !ok { 73 | return nil, fmt.Errorf("username key %s not found in secret %s", usernameKey, pullSecret.SecretName) 74 | } 75 | 76 | password, ok := secret.Data[passwordKey] 77 | if !ok { 78 | return nil, fmt.Errorf("password key %s not found in secret %s", passwordKey, pullSecret.SecretName) 79 | } 80 | 81 | return credentialFuncFromCredentials(auth.Credential{ 82 | Username: string(username), 83 | Password: string(password), 84 | }), nil 85 | } 86 | -------------------------------------------------------------------------------- /internal/pkg/image/const.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package image 18 | 19 | const ( 20 | // Registry the default registry used for Falco images. 21 | Registry = "docker.io" 22 | // Repository the default repository used for Falco images. 23 | Repository = "falcosecurity" 24 | // FalcoImage the default image name used for Falco. 25 | FalcoImage = "falco" 26 | // FalcoTag the default tag used for Falco. 27 | FalcoTag = "0.41.0" 28 | ) 29 | -------------------------------------------------------------------------------- /internal/pkg/image/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package image contains helper functions for images. 18 | package image 19 | -------------------------------------------------------------------------------- /internal/pkg/image/image.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package image 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | ) 23 | 24 | // BuildImageString constructs the image string from registry, repository, image, and tag. 25 | func BuildImageString(registry, repository, image, tag string) string { 26 | return fmt.Sprintf("%s/%s/%s:%s", registry, repository, image, tag) 27 | } 28 | 29 | // BuildFalcoImageStringFromVersion constructs the image string for Falco. 30 | func BuildFalcoImageStringFromVersion(version string) string { 31 | if version == "" { 32 | return BuildImageString(Registry, Repository, FalcoImage, FalcoTag) 33 | } 34 | 35 | return fmt.Sprintf("%s/%s/%s:%s", Registry, Repository, FalcoImage, version) 36 | } 37 | 38 | // FalcoVersion returns the version of Falco specified in the FalcoTag. 39 | func FalcoVersion() string { 40 | return strings.Split(FalcoTag, "-")[0] 41 | } 42 | 43 | // VersionFromImage returns the version from the image string. 44 | func VersionFromImage(image string) string { 45 | parts := strings.Split(image, ":") 46 | if len(parts) == 2 { 47 | return parts[1] 48 | } 49 | return "" 50 | } 51 | -------------------------------------------------------------------------------- /internal/pkg/image/image_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package image 18 | 19 | import ( 20 | "strings" 21 | "testing" 22 | ) 23 | 24 | func TestBuildImageString(t *testing.T) { 25 | tests := []struct { 26 | name string 27 | registry string 28 | repo string 29 | image string 30 | tag string 31 | want string 32 | }{ 33 | { 34 | name: "basic image string", 35 | registry: "docker.io", 36 | repo: "falcosecurity", 37 | image: "falco", 38 | tag: "latest", 39 | want: "docker.io/falcosecurity/falco:latest", 40 | }, 41 | { 42 | name: "custom registry", 43 | registry: "custom.registry.io", 44 | repo: "falcosecurity", 45 | image: "falco", 46 | tag: "0.1.0", 47 | want: "custom.registry.io/falcosecurity/falco:0.1.0", 48 | }, 49 | } 50 | 51 | for _, tt := range tests { 52 | t.Run(tt.name, func(t *testing.T) { 53 | got := BuildImageString(tt.registry, tt.repo, tt.image, tt.tag) 54 | if got != tt.want { 55 | t.Errorf("BuildImageString() = %v, want %v", got, tt.want) 56 | } 57 | }) 58 | } 59 | } 60 | 61 | func TestBuildFalcoImageStringFromVersion(t *testing.T) { 62 | tests := []struct { 63 | name string 64 | version string 65 | want string 66 | }{ 67 | { 68 | name: "empty version", 69 | version: "", 70 | want: BuildImageString(Registry, Repository, FalcoImage, FalcoTag), 71 | }, 72 | { 73 | name: "specific version", 74 | version: "0.1.0", 75 | want: BuildImageString(Registry, Repository, FalcoImage, "0.1.0"), 76 | }, 77 | { 78 | name: "version with prefix", 79 | version: "v0.1.0", 80 | want: BuildImageString(Registry, Repository, FalcoImage, "v0.1.0"), 81 | }, 82 | } 83 | 84 | for _, tt := range tests { 85 | t.Run(tt.name, func(t *testing.T) { 86 | got := BuildFalcoImageStringFromVersion(tt.version) 87 | if got != tt.want { 88 | t.Errorf("BuildFalcoImageStringFromVersion() = %v, want %v", got, tt.want) 89 | } 90 | }) 91 | } 92 | } 93 | 94 | func TestFalcoVersion(t *testing.T) { 95 | tests := []struct { 96 | name string 97 | want string 98 | }{ 99 | { 100 | name: "extract version from FalcoTag", 101 | want: strings.Split(FalcoTag, "-")[0], 102 | }, 103 | } 104 | 105 | for _, tt := range tests { 106 | t.Run(tt.name, func(t *testing.T) { 107 | got := FalcoVersion() 108 | if got != tt.want { 109 | t.Errorf("FalcoVersion() = %v, want %v", got, tt.want) 110 | } 111 | }) 112 | } 113 | } 114 | 115 | func TestVersionFromImage(t *testing.T) { 116 | tests := []struct { 117 | name string 118 | image string 119 | want string 120 | }{ 121 | { 122 | name: "valid image with tag", 123 | image: "docker.io/falcosecurity/falco:0.1.0", 124 | want: "0.1.0", 125 | }, 126 | { 127 | name: "image without tag", 128 | image: "docker.io/falcosecurity/falco", 129 | want: "", 130 | }, 131 | { 132 | name: "empty string", 133 | image: "", 134 | want: "", 135 | }, 136 | } 137 | 138 | for _, tt := range tests { 139 | t.Run(tt.name, func(t *testing.T) { 140 | got := VersionFromImage(tt.image) 141 | if got != tt.want { 142 | t.Errorf("VersionFromImage() = %v, want %v", got, tt.want) 143 | } 144 | }) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /internal/pkg/mounts/consts.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package mounts 18 | 19 | const ( 20 | // ConfigDirPath mount path for empty dir where Falco's configuration files are store by 21 | // the artifact-operator. 22 | ConfigDirPath = "/etc/falco/config.d" 23 | // ConfigMountName is the name of the volume mount for Falco's configuration files. 24 | ConfigMountName = "falco-configs" 25 | // RulesfileDirPath mount path for empty dir where Falco's rules files are stored by the 26 | // artifact-operator. 27 | RulesfileDirPath = "/etc/falco/rules.d" 28 | // RulesfileMountName is the name of the volume mount for Falco's rules files. 29 | RulesfileMountName = "falco-rulesfiles" 30 | // PluginDirPath mount path for empty dir where Falco's plugins are stored by the 31 | // artifact-operator. 32 | PluginDirPath = "/usr/share/falco/plugins" 33 | // PluginMountName is the name of the volume mount for Falco's plugins. 34 | PluginMountName = "falco-plugins" 35 | ) 36 | -------------------------------------------------------------------------------- /internal/pkg/mounts/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package mounts provides the logic for mounting volumes to a container. 18 | package mounts 19 | -------------------------------------------------------------------------------- /internal/pkg/oci/client/client.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package client 18 | 19 | import ( 20 | "net/http" 21 | 22 | "oras.land/oras-go/v2/registry/remote/auth" 23 | "oras.land/oras-go/v2/registry/remote/retry" 24 | ) 25 | 26 | const defaultClientID = "falco-artifact-operator" 27 | 28 | // Option represents a client option function. 29 | type Option func(*auth.Client) 30 | 31 | // NewClient creates a new ORAS client with the provided options. 32 | func NewClient(opts ...Option) *auth.Client { 33 | client := &auth.Client{ 34 | Client: retry.DefaultClient, 35 | Header: http.Header{ 36 | "User-Agent": {defaultClientID}, 37 | }, 38 | Cache: auth.DefaultCache, 39 | ClientID: defaultClientID, 40 | } 41 | 42 | // Apply all provided options 43 | for _, opt := range opts { 44 | opt(client) 45 | } 46 | 47 | return client 48 | } 49 | 50 | // WithClientID configures the client with a custom client ID for OAuth2. 51 | func WithClientID(clientID string) Option { 52 | return func(c *auth.Client) { 53 | c.ClientID = clientID 54 | } 55 | } 56 | 57 | // WithForceOAuth2 configures the client to always attempt OAuth2 authentication. 58 | func WithForceOAuth2(force bool) Option { 59 | return func(c *auth.Client) { 60 | c.ForceAttemptOAuth2 = force 61 | } 62 | } 63 | 64 | // WithCredentialFunc configures the client with a custom credential function. 65 | func WithCredentialFunc(credFunc auth.CredentialFunc) Option { 66 | return func(c *auth.Client) { 67 | c.Credential = credFunc 68 | } 69 | } 70 | 71 | // WithTransport configures the client with a custom HTTP transport. 72 | func WithTransport(transport http.RoundTripper) Option { 73 | return func(c *auth.Client) { 74 | c.Client.Transport = transport 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /internal/pkg/oci/client/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package client contains the logic to interact with the OCI registry. 18 | // It provides a client to interact with the OCI registry. 19 | package client 20 | -------------------------------------------------------------------------------- /internal/pkg/oci/puller/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package puller provides a way to pull OCI images from a registry. 18 | // It uses the oras library to pull images. 19 | package puller 20 | -------------------------------------------------------------------------------- /internal/pkg/oci/puller/puller.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package puller 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | 25 | v1 "github.com/opencontainers/image-spec/specs-go/v1" 26 | "oras.land/oras-go/v2" 27 | "oras.land/oras-go/v2/content/file" 28 | "oras.land/oras-go/v2/registry/remote" 29 | ) 30 | 31 | // Puller implements pull operations. 32 | type Puller struct { 33 | Client remote.Client 34 | plainHTTP bool 35 | } 36 | 37 | // NewPuller create a new puller that can be used for pull operations. 38 | // The client must be ready to be used by the puller. 39 | func NewPuller(client remote.Client, plainHTTP bool) *Puller { 40 | return &Puller{ 41 | Client: client, 42 | plainHTTP: plainHTTP, 43 | } 44 | } 45 | 46 | // Pull an artifact from a remote registry. 47 | // Ref format follows: REGISTRY/REPO[:TAG|@DIGEST]. Ex. localhost:5000/hello:latest. 48 | func (p *Puller) Pull(ctx context.Context, ref, destDir, os, arch string) (*RegistryResult, error) { 49 | fileStore, err := file.New(destDir) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | repo, err := remote.NewRepository(ref) 55 | if err != nil { 56 | return nil, fmt.Errorf("unable to create new repository with ref %s: %w", ref, err) 57 | } 58 | 59 | repo.Client = p.Client 60 | repo.PlainHTTP = p.plainHTTP 61 | 62 | // if no tag was specified, "latest" is used 63 | if repo.Reference.Reference == "" { 64 | ref += ":" + DefaultTag 65 | repo.Reference.Reference = DefaultTag 66 | } 67 | 68 | refDesc, _, err := repo.FetchReference(ctx, ref) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | copyOpts := oras.CopyOptions{} 74 | copyOpts.Concurrency = 1 75 | if refDesc.MediaType == v1.MediaTypeImageIndex { 76 | plt := &v1.Platform{ 77 | OS: os, 78 | Architecture: arch, 79 | } 80 | copyOpts.WithTargetPlatform(plt) 81 | } 82 | 83 | localTarget := oras.Target(fileStore) 84 | 85 | desc, err := oras.Copy(ctx, repo, ref, localTarget, ref, copyOpts) 86 | 87 | if err != nil { 88 | return nil, fmt.Errorf("unable to pull artifact %s with tag %s from repo %s: %w", 89 | repo.Reference.Repository, repo.Reference.Reference, repo.Reference.Repository, err) 90 | } 91 | 92 | manifest, err := manifestFromDesc(ctx, localTarget, &desc) 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | var artifactType ArtifactType 98 | switch manifest.Layers[0].MediaType { 99 | case FalcoPluginLayerMediaType: 100 | artifactType = Plugin 101 | case FalcoRulesfileLayerMediaType: 102 | artifactType = Rulesfile 103 | case FalcoAssetLayerMediaType: 104 | artifactType = Asset 105 | default: 106 | return nil, fmt.Errorf("unknown media type: %q", manifest.Layers[0].MediaType) 107 | } 108 | 109 | filename := manifest.Layers[0].Annotations[v1.AnnotationTitle] 110 | 111 | return &RegistryResult{ 112 | RootDigest: string(refDesc.Digest), 113 | Digest: string(desc.Digest), 114 | Type: artifactType, 115 | Filename: filename, 116 | }, nil 117 | } 118 | 119 | func manifestFromDesc(ctx context.Context, target oras.Target, desc *v1.Descriptor) (*v1.Manifest, error) { 120 | var manifest v1.Manifest 121 | 122 | descReader, err := target.Fetch(ctx, *desc) 123 | if err != nil { 124 | return nil, fmt.Errorf("unable to fetch descriptor with digest %q: %w", desc.Digest, err) 125 | } 126 | 127 | descBytes, err := io.ReadAll(descReader) 128 | if err != nil { 129 | return nil, fmt.Errorf("unable to read bytes from descriptor: %w", err) 130 | } 131 | 132 | if err = json.Unmarshal(descBytes, &manifest); err != nil { 133 | return nil, fmt.Errorf("unable to unmarshal manifest: %w", err) 134 | } 135 | 136 | if len(manifest.Layers) < 1 { 137 | return nil, fmt.Errorf("no layers in manifest") 138 | } 139 | 140 | return &manifest, nil 141 | } 142 | -------------------------------------------------------------------------------- /internal/pkg/oci/puller/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package puller 18 | 19 | import ( 20 | "errors" 21 | ) 22 | 23 | // ArtifactType represents a rules file or a plugin. Used to select the right mediaType when interacting with the registry. 24 | type ArtifactType string 25 | 26 | const ( 27 | // Rulesfile represents a rules file artifact. 28 | Rulesfile ArtifactType = "rulesfile" 29 | // Plugin represents a plugin artifact. 30 | Plugin ArtifactType = "plugin" 31 | // Asset represents an artifact consumed by another plugin. 32 | Asset ArtifactType = "asset" 33 | 34 | // FalcoRulesfileConfigMediaType is the MediaType for rule's config layer. 35 | FalcoRulesfileConfigMediaType = "application/vnd.cncf.falco.rulesfile.config.v1+json" 36 | 37 | // FalcoRulesfileLayerMediaType is the MediaType for rules. 38 | FalcoRulesfileLayerMediaType = "application/vnd.cncf.falco.rulesfile.layer.v1+tar.gz" 39 | 40 | // FalcoPluginConfigMediaType is the MediaType for plugin's config layer. 41 | FalcoPluginConfigMediaType = "application/vnd.cncf.falco.plugin.config.v1+json" 42 | 43 | // FalcoPluginLayerMediaType is the MediaType for plugins. 44 | FalcoPluginLayerMediaType = "application/vnd.cncf.falco.plugin.layer.v1+tar.gz" 45 | 46 | // FalcoAssetConfigMediaType is the MediaType for asset's config layer. 47 | FalcoAssetConfigMediaType = "application/vnd.cncf.falco.asset.config.v1+json" 48 | 49 | // FalcoAssetLayerMediaType is the MediaType for assets. 50 | FalcoAssetLayerMediaType = "application/vnd.cncf.falco.asset.layer.v1+tar.gz" 51 | 52 | // DefaultTag is the default tag reference to be used when none is provided. 53 | DefaultTag = "latest" 54 | ) 55 | 56 | // The following functions are necessary to use ArtifactType with Cobra. 57 | 58 | // String returns a string representation of ArtifactType. 59 | func (e ArtifactType) String() string { 60 | return string(e) 61 | } 62 | 63 | // Set an ArtifactType. 64 | func (e *ArtifactType) Set(v string) error { 65 | switch v { 66 | case "rulesfile", "plugin", "asset": 67 | *e = ArtifactType(v) 68 | return nil 69 | default: 70 | return errors.New(`must be one of "rulesfile", "plugin", "asset"`) 71 | } 72 | } 73 | 74 | // Type returns a string representing this type. 75 | func (e *ArtifactType) Type() string { 76 | return "ArtifactType" 77 | } 78 | 79 | // RegistryResult represents a generic result that is generated when 80 | // interacting with a remote OCI registry. 81 | type RegistryResult struct { 82 | RootDigest string 83 | Digest string 84 | Config ArtifactConfig 85 | Type ArtifactType 86 | Filename string 87 | } 88 | 89 | // ArtifactConfig is the struct stored in the config layer of rulesfile and plugin artifacts. Each type fills only the fields of interest. 90 | type ArtifactConfig struct { 91 | // It's the unique name used by the index 92 | Name string `json:"name,omitempty"` 93 | Version string `json:"version,omitempty"` 94 | Dependencies []ArtifactDependency `json:"dependencies,omitempty"` 95 | Requirements []ArtifactRequirement `json:"requirements,omitempty"` 96 | } 97 | 98 | // ArtifactRequirement represents the artifact's requirement to be stored in the config. 99 | type ArtifactRequirement struct { 100 | Name string `json:"name"` 101 | Version string `json:"version"` 102 | } 103 | 104 | // Dependency represent a dependency with its own name and version. 105 | type Dependency struct { 106 | Name string `json:"name"` 107 | Version string `json:"version"` 108 | } 109 | 110 | // ArtifactDependency represents the artifact's depedendency to be stored in the config. 111 | type ArtifactDependency struct { 112 | Name string `json:"name"` 113 | Version string `json:"version"` 114 | Alternatives []Dependency `json:"alternatives,omitempty"` 115 | } 116 | -------------------------------------------------------------------------------- /internal/pkg/priority/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package priority provides logic to extract and validate the priority of an artifact. 18 | package priority 19 | -------------------------------------------------------------------------------- /internal/pkg/priority/priority.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package priority 18 | 19 | import ( 20 | "fmt" 21 | ) 22 | 23 | const ( 24 | // MaxPriority is the maximum value for the priority annotation. 25 | MaxPriority = 99 26 | // MinPriority is the minimum value for the priority annotation. 27 | MinPriority = 0 28 | // DefaultPriority is the default priority value used when the priority annotation is not present in the artifact. 29 | DefaultPriority = 50 30 | // OCISubPriority is the sub-priority value for OCI-based artifacts. 31 | OCISubPriority = 1 32 | // CMSubPriority is the sub-priority value for ConfigMap-based artifacts. 33 | CMSubPriority = 2 34 | // InLineRulesSubPriority is the sub-priority value for raw YAML-based artifacts. 35 | InLineRulesSubPriority = 3 36 | ) 37 | 38 | // NameFromPriority generates a name by combining the priority and original name. 39 | func NameFromPriority(priority int32, originalName string) string { 40 | return fmt.Sprintf("%0*d-%s", 2, priority, originalName) 41 | } 42 | 43 | // NameFromPriorityAndSubPriority generates a name by combining the priority, sub-priority, and original name. 44 | // It takes priority, subPriority and originalName as inputs and returns a string formatted as "priority-subPriority-originalName". 45 | func NameFromPriorityAndSubPriority(priority, subPriority int32, originalName string) string { 46 | return fmt.Sprintf("%0*d-%0*d-%s", 2, priority, 2, subPriority, originalName) 47 | } 48 | -------------------------------------------------------------------------------- /internal/pkg/version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package version provides version information for the build. 18 | package version 19 | 20 | import ( 21 | "fmt" 22 | "runtime" 23 | ) 24 | 25 | var ( 26 | // SemVersion indicates the semantic version of the build. 27 | SemVersion = "v0.0.0-master" 28 | 29 | // GitCommit indicates the git commit hash of the build. 30 | GitCommit = "" 31 | 32 | // BuildDate indicates the date when the build was created. 33 | BuildDate = "1970-01-01T00:00:00Z" 34 | 35 | // Compiler indicates the compiler used for the build. 36 | Compiler = runtime.Compiler 37 | 38 | // Platform indicates the operating system and architecture of the build. 39 | Platform = fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) 40 | ) 41 | -------------------------------------------------------------------------------- /test/e2e/e2e_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | package e2e 18 | 19 | import ( 20 | "fmt" 21 | "os/exec" 22 | "testing" 23 | 24 | . "github.com/onsi/ginkgo/v2" 25 | . "github.com/onsi/gomega" 26 | 27 | "github.com/falcosecurity/falco-operator/test/utils" 28 | ) 29 | 30 | var ( 31 | projectImage = "falcosecurity/falco-operator:latest" 32 | ) 33 | 34 | // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, 35 | // temporary environment to validate project changes with the the purposed to be used in CI jobs. 36 | // The default setup requires Kind, builds/loads the Manager Docker image locally, and installs 37 | // CertManager and Prometheus. 38 | func TestE2E(t *testing.T) { 39 | RegisterFailHandler(Fail) 40 | _, _ = fmt.Fprintf(GinkgoWriter, "Starting falco-operator integration test suite\n") 41 | RunSpecs(t, "e2e suite") 42 | } 43 | 44 | var _ = BeforeSuite(func() { 45 | By("building the manager(Operator) image") 46 | cmd := exec.Command("make", "docker-build", "OPERATOR=falco", fmt.Sprintf("IMG=%s", projectImage)) 47 | _, err := utils.Run(cmd) 48 | ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image") 49 | 50 | // TODO(user): If you want to change the e2e test vendor from Kind, ensure the image is 51 | // built and available before running the tests. Also, remove the following block. 52 | By("loading the manager(Operator) image on Kind") 53 | err = utils.LoadImageToKindClusterWithName(projectImage) 54 | ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind") 55 | }) 56 | -------------------------------------------------------------------------------- /test/utils/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2025 The Falco Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | 17 | // Package utils defines helper functions used to write tests. 18 | package utils 19 | --------------------------------------------------------------------------------