Compare commits

..

No commits in common. "main" and "v0.7.0-rc1" have entirely different histories.

97 changed files with 2245 additions and 4706 deletions

View File

@ -1,6 +1,6 @@
<!-- Thanks for sending a pull request! Here are some tips for you: <!-- Thanks for sending a pull request! Here are some tips for you:
1. If this is your first time, please read our contributor guidelines in the [CONTRIBUTING.md](https://github.com/falcosecurity/.github/blob/main/CONTRIBUTING.md) file in the Falco `.github` repository. 1. If this is your first time, please read our contributor guidelines in the [CONTRIBUTING.md](https://github.com/falcosecurity/falco/blob/dev/CONTRIBUTING.md) file in the Falco repository.
2. Please label this pull request according to what type of issue you are addressing. 2. Please label this pull request according to what type of issue you are addressing.
3. Please add a release note! 3. Please add a release note!
4. If the PR is unfinished while opening it specify a wip in the title before the actual title, for example, "wip: my awesome feature" 4. If the PR is unfinished while opening it specify a wip in the title before the actual title, for example, "wip: my awesome feature"

View File

@ -1,22 +0,0 @@
version: 2
updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
groups:
gomod:
update-types:
- "patch"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
groups:
actions:
update-types:
- "minor"
- "patch"

View File

@ -26,7 +26,7 @@ jobs:
- go - go
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@d23060145bc9131d50558d5d4185494a20208101 # v2.2.8 uses: github/codeql-action/init@d23060145bc9131d50558d5d4185494a20208101 # v2.2.8
with: with:

View File

@ -21,10 +21,6 @@ on:
description: The digest of the pushed image. description: The digest of the pushed image.
value: ${{ jobs.docker-image.outputs.digest }} value: ${{ jobs.docker-image.outputs.digest }}
permissions:
contents: read
id-token: write
jobs: jobs:
docker-image: docker-image:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@ -33,43 +29,30 @@ jobs:
digest: ${{ steps.build-and-push.outputs.digest }} digest: ${{ steps.build-and-push.outputs.digest }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: Buildx id: Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c # v2.5.0
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
with: with:
username: ${{ secrets.DOCKERHUB_USER }} username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_SECRET }} password: ${{ secrets.DOCKERHUB_SECRET }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
with:
role-to-assume: arn:aws:iam::292999226676:role/github_actions-falcoctl-ecr
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr-public
uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1
with:
registry-type: public
- name: Docker Meta - name: Docker Meta
id: meta_falcoctl id: meta_falcoctl
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v4.3.0
with: with:
# list of Docker images to use as base name for tags # list of Docker images to use as base name for tags
images: | images: |
docker.io/falcosecurity/falcoctl docker.io/falcosecurity/falcoctl
public.ecr.aws/falcosecurity/falcoctl
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=semver,pattern={{ version }} type=semver,pattern={{ version }}
@ -78,7 +61,7 @@ jobs:
- name: Build and push - name: Build and push
id: build-and-push id: build-and-push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # v4.0.0
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
@ -92,7 +75,7 @@ jobs:
- name: Install Cosign - name: Install Cosign
if: ${{ inputs.sign }} if: ${{ inputs.sign }}
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 uses: sigstore/cosign-installer@9614fae9e5c5eddabb09f90a270fcb487c9f7149 # v3.3.0
- name: Sign the images with GitHub OIDC Token - name: Sign the images with GitHub OIDC Token
if: ${{ inputs.sign }} if: ${{ inputs.sign }}

View File

@ -23,14 +23,14 @@ jobs:
goos: windows goos: windows
steps: steps:
- name: Checkout commit - name: Checkout commit
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version-file: 'go.mod' go-version: '1.21'
check-latest: true check-latest: true
- name: Build Falcoctl - name: Build Falcoctl
@ -47,14 +47,14 @@ jobs:
tar -czvf falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz falcoctl LICENSE tar -czvf falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz falcoctl LICENSE
- name: Upload falcoctl artifacts - name: Upload falcoctl artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: falcoctl-${{ matrix.goos }}-${{ matrix.goarch }} name: falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}
path: ./falcoctl-${{ matrix.goos }}-${{ matrix.goarch }} path: ./falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}
retention-days: 1 retention-days: 1
- name: Upload falcoctl archives - name: Upload falcoctl archives
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz name: falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
path: ./falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz path: ./falcoctl-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
@ -80,86 +80,22 @@ jobs:
needs: docker-configure needs: docker-configure
uses: ./.github/workflows/docker-image.yaml uses: ./.github/workflows/docker-image.yaml
secrets: inherit secrets: inherit
permissions:
contents: read
id-token: write
with: with:
release: ${{ needs.docker-configure.outputs.release }} release: ${{ needs.docker-configure.outputs.release }}
commit: ${{ needs.docker-configure.outputs.commit }} commit: ${{ needs.docker-configure.outputs.commit }}
build_date: ${{ needs.docker-configure.outputs.build_date }} build_date: ${{ needs.docker-configure.outputs.build_date }}
sign: true
provenance-for-images-docker:
if: ${{ github.event_name == 'push' }}
needs: [docker-configure, docker-image]
permissions:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
with:
image: docker.io/falcosecurity/falcoctl
# The image digest is used to prevent TOCTOU issues.
# This is an output of the docker/build-push-action
# See: https://github.com/slsa-framework/slsa-verifier#toctou-attacks
digest: ${{ needs.docker-image.outputs.digest }}
secrets:
registry-username: ${{ secrets.DOCKERHUB_USER }}
registry-password: ${{ secrets.DOCKERHUB_SECRET }}
login-to-amazon-ecr:
if: ${{ github.event_name == 'push' }}
runs-on: ubuntu-22.04
permissions:
contents: read
id-token: write
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
with:
role-to-assume: arn:aws:iam::292999226676:role/github_actions-falcoctl-ecr
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr-public
uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1
with:
registry-type: public
mask-password: 'false'
outputs:
registry: ${{ steps.login-ecr-public.outputs.registry }}
docker_username: ${{ steps.login-ecr-public.outputs.docker_username_public_ecr_aws }}
docker_password: ${{ steps.login-ecr-public.outputs.docker_password_public_ecr_aws }}
provenance-for-images-aws-ecr:
if: ${{ github.event_name == 'push' }}
needs: [docker-configure, docker-image, login-to-amazon-ecr]
permissions:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
with:
image: public.ecr.aws/falcosecurity/falcoctl
# The image digest is used to prevent TOCTOU issues.
# This is an output of the docker/build-push-action
# See: https://github.com/slsa-framework/slsa-verifier#toctou-attacks
digest: ${{ needs.docker-image.outputs.digest }}
secrets:
registry-username: ${{ needs.login-to-amazon-ecr.outputs.docker_username }}
registry-password: ${{ needs.login-to-amazon-ecr.outputs.docker_password }}
test: test:
needs: build needs: build
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout commit - name: Checkout commit
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version-file: 'go.mod' go-version: '1.21'
check-latest: true check-latest: true
- name: Run tests - name: Run tests

View File

@ -8,25 +8,24 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
repository: ${{github.event.pull_request.head.repo.full_name}} repository: ${{github.event.pull_request.head.repo.full_name}}
persist-credentials: false persist-credentials: false
- name: Setup Go - name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version: "^1.24.3" go-version: '1.21'
go-version-file: "go.mod"
check-latest: true check-latest: true
cache: "false" cache: 'false'
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
with: with:
only-new-issues: true only-new-issues: true
version: v1.64.7 version: v1.54.2
args: --timeout=900s args: --timeout=900s
gomodtidy: gomodtidy:
@ -35,16 +34,16 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
with: with:
ref: "${{ github.event.pull_request.head.sha }}" ref: "${{ github.event.pull_request.head.sha }}"
repository: ${{github.event.pull_request.head.repo.full_name}} repository: ${{github.event.pull_request.head.repo.full_name}}
persist-credentials: false persist-credentials: false
- name: Setup Go - name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version-file: "go.mod" go-version: '1.21'
check-latest: true check-latest: true
- name: Execute go mod tidy and check the outcome - name: Execute go mod tidy and check the outcome

View File

@ -14,7 +14,7 @@ jobs:
hashes: ${{ steps.hash.outputs.hashes }} hashes: ${{ steps.hash.outputs.hashes }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
with: with:
fetch-depth: 0 fetch-depth: 0
@ -22,14 +22,14 @@ jobs:
run: git fetch --force --tags run: git fetch --force --tags
- name: Setup Go - name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0
with: with:
go-version-file: 'go.mod' go-version: '1.21'
check-latest: true check-latest: true
- name: Run GoReleaser - name: Run GoReleaser
id: run-goreleaser id: run-goreleaser
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4.2.0
with: with:
distribution: goreleaser distribution: goreleaser
version: latest version: latest
@ -53,7 +53,7 @@ jobs:
actions: read # To read the workflow path. actions: read # To read the workflow path.
id-token: write # To sign the provenance. id-token: write # To sign the provenance.
contents: write # To add assets to a release. contents: write # To add assets to a release.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.6.0
with: with:
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}" base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
upload-assets: true # upload to a new release upload-assets: true # upload to a new release
@ -64,7 +64,7 @@ jobs:
permissions: read-all permissions: read-all
steps: steps:
- name: Install the verifier - name: Install the verifier
uses: slsa-framework/slsa-verifier/actions/installer@v2.7.1 uses: slsa-framework/slsa-verifier/actions/installer@v2.3.0
- name: Download assets - name: Download assets
env: env:
@ -120,13 +120,13 @@ jobs:
build_date: ${{ needs.docker-configure.outputs.build_date }} build_date: ${{ needs.docker-configure.outputs.build_date }}
sign: true sign: true
provenance-for-images-docker: provenance-for-images:
needs: [docker-configure, docker-image] needs: [docker-configure, docker-image]
permissions: permissions:
actions: read # for detecting the Github Actions environment. actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing. id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations. packages: write # for uploading attestations.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0 uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
with: with:
image: docker.io/falcosecurity/falcoctl image: docker.io/falcosecurity/falcoctl
# The image digest is used to prevent TOCTOU issues. # The image digest is used to prevent TOCTOU issues.
@ -136,43 +136,3 @@ jobs:
secrets: secrets:
registry-username: ${{ secrets.DOCKERHUB_USER }} registry-username: ${{ secrets.DOCKERHUB_USER }}
registry-password: ${{ secrets.DOCKERHUB_SECRET }} registry-password: ${{ secrets.DOCKERHUB_SECRET }}
login-to-amazon-ecr:
runs-on: ubuntu-22.04
permissions:
contents: read
id-token: write
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.2.1
with:
role-to-assume: arn:aws:iam::292999226676:role/github_actions-falcoctl-ecr
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr-public
uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1
with:
registry-type: public
mask-password: 'false'
outputs:
registry: ${{ steps.login-ecr-public.outputs.registry }}
docker_username: ${{ steps.login-ecr-public.outputs.docker_username_public_ecr_aws }}
docker_password: ${{ steps.login-ecr-public.outputs.docker_password_public_ecr_aws }}
provenance-for-images-aws-ecr:
needs: [docker-configure, docker-image, login-to-amazon-ecr]
permissions:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
with:
image: public.ecr.aws/falcosecurity/falcoctl
# The image digest is used to prevent TOCTOU issues.
# This is an output of the docker/build-push-action
# See: https://github.com/slsa-framework/slsa-verifier#toctou-attacks
digest: ${{ needs.docker-image.outputs.digest }}
secrets:
registry-username: ${{ needs.login-to-amazon-ecr.outputs.docker_username }}
registry-password: ${{ needs.login-to-amazon-ecr.outputs.docker_password }}

View File

@ -44,11 +44,17 @@ linters-settings:
- opinionated - opinionated
- performance - performance
- style - style
disabled-checks:
# Conflicts with govet check-shadowing
- sloppyReassign
goimports: goimports:
local-prefixes: github.com/falcosecurity/falcoctl local-prefixes: github.com/falcosecurity/falcoctl
govet:
check-shadowing: true
misspell: misspell:
locale: US locale: US
nolintlint: nolintlint:
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
allow-unused: false # report any unused nolint directives allow-unused: false # report any unused nolint directives
require-explanation: true # require an explanation for nolint directives require-explanation: true # require an explanation for nolint directives
require-specific: true # require nolint directives to be specific about which linter is being skipped require-specific: true # require nolint directives to be specific about which linter is being skipped
@ -65,7 +71,7 @@ linters:
- errcheck - errcheck
- errorlint - errorlint
- exhaustive - exhaustive
- copyloopvar - exportloopref
# - funlen # - funlen
# - gochecknoglobals # - gochecknoglobals
# - gochecknoinits # - gochecknoinits

View File

@ -1,5 +1,3 @@
version: 2
project_name: falcoctl project_name: falcoctl
before: before:
hooks: hooks:
@ -18,8 +16,6 @@ builds:
ignore: ignore:
- goos: darwin - goos: darwin
goarch: 386 goarch: 386
- goos: windows
goarch: 386
ldflags: | ldflags: |
-X github.com/falcosecurity/falcoctl/cmd/version.buildDate={{ .Date }} -X github.com/falcosecurity/falcoctl/cmd/version.buildDate={{ .Date }}
@ -47,6 +43,3 @@ release:
changelog: changelog:
use: github-native use: github-native
git:
tag_sort: -version:creatordate

View File

@ -18,7 +18,6 @@ PROJECT?=github.com/falcosecurity/falcoctl
# todo(leogr): re-enable race when CLI tests can run with race enabled # todo(leogr): re-enable race when CLI tests can run with race enabled
TEST_FLAGS ?= -v -cover# -race TEST_FLAGS ?= -v -cover# -race
.PHONY: falcoctl
falcoctl: falcoctl:
$(GO) build -ldflags \ $(GO) build -ldflags \
"-X '${PROJECT}/cmd/version.semVersion=${RELEASE}' \ "-X '${PROJECT}/cmd/version.semVersion=${RELEASE}' \
@ -63,7 +62,7 @@ fmt: gci addlicense
.PHONY: golangci-lint .PHONY: golangci-lint
golangci-lint: golangci-lint:
ifeq (, $(shell which golangci-lint)) ifeq (, $(shell which golangci-lint))
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 @go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2
GOLANGCILINT=$(GOBIN)/golangci-lint GOLANGCILINT=$(GOBIN)/golangci-lint
else else
GOLANGCILINT=$(shell which golangci-lint) GOLANGCILINT=$(shell which golangci-lint)

1
OWNERS
View File

@ -4,6 +4,7 @@ approvers:
- maxgio92 - maxgio92
- fededp - fededp
- cpanato - cpanato
reviewers:
- alacuku - alacuku
- loresuso - loresuso
emeritus_approvers: emeritus_approvers:

View File

@ -23,13 +23,6 @@ sudo install -o root -g root -m 0755 falcoctl /usr/local/bin/falcoctl
> NOTE: Make sure */usr/local/bin* is in your PATH environment variable. > NOTE: Make sure */usr/local/bin* is in your PATH environment variable.
#### MacOS #### MacOS
The easiest way to install on MacOS is via `Homebrew`:
```bash
brew install falcoctl
```
Alternatively, you can download directly from the source:
##### Intel ##### Intel
```bash ```bash
LATEST=$(curl -sI https://github.com/falcosecurity/falcoctl/releases/latest | awk '/location: /{gsub("\r","",$2);split($2,v,"/");print substr(v[8],2)}') LATEST=$(curl -sI https://github.com/falcosecurity/falcoctl/releases/latest | awk '/location: /{gsub("\r","",$2);split($2,v,"/");print substr(v[8],2)}')
@ -216,8 +209,6 @@ Indices for *falcoctl* can be retrieved from various storage backends. The suppo
| http | http:// | Can be used to retrieve indices via simple HTTP GET requests. | | http | http:// | Can be used to retrieve indices via simple HTTP GET requests. |
| https | https:// | Convenience alias for the HTTP backend. | | https | https:// | Convenience alias for the HTTP backend. |
| gcs | gs:// | For indices stored as Google Cloud Storage objects. Supports application default credentials. | | gcs | gs:// | For indices stored as Google Cloud Storage objects. Supports application default credentials. |
| file | file:// | For indices stored on the local file system. |
| s3 | s3:// | For indices stored as AWS S3 objects. Supports default credentials, IRSA. |
#### falcoctl index add #### falcoctl index add
@ -344,7 +335,6 @@ $ falcoctl registry push --type=plugin ghcr.io/falcosecurity/plugins/plugin/clou
``` ```
The type denotes the **artifact** type in this case *plugins*. The `ghcr.io/falcosecurity/plugins/plugin/cloudtrail:0.3.0` is the unique reference that points to the **artifact**. The type denotes the **artifact** type in this case *plugins*. The `ghcr.io/falcosecurity/plugins/plugin/cloudtrail:0.3.0` is the unique reference that points to the **artifact**.
Currently, *falcoctl* supports only two types of artifacts: **plugin** and **rulesfile**. Based on **artifact type** the commands accepts different flags: Currently, *falcoctl* supports only two types of artifacts: **plugin** and **rulesfile**. Based on **artifact type** the commands accepts different flags:
* `--add-floating-tags`: add the floating tags for the major and minor versions
* `--annotation-source`: set annotation source for the artifact; * `--annotation-source`: set annotation source for the artifact;
* `--depends-on`: set an artifact dependency (can be specified multiple times). Example: `--depends-on my-plugin:1.2.3` * `--depends-on`: set an artifact dependency (can be specified multiple times). Example: `--depends-on my-plugin:1.2.3`
* `--tag`: additional artifact tag. Can be repeated multiple time * `--tag`: additional artifact tag. Can be repeated multiple time

View File

@ -1,4 +1,4 @@
FROM cgr.dev/chainguard/go AS builder FROM golang:1.21 as builder
WORKDIR /tmp/builder WORKDIR /tmp/builder
ARG RELEASE ARG RELEASE
@ -29,8 +29,14 @@ RUN CGO_ENABLED=0 \
RUN echo ${RELEASE} RUN echo ${RELEASE}
FROM cgr.dev/chainguard/static:latest FROM alpine:3.18.4
COPY --from=builder /tmp/builder/falcoctl /usr/bin/falcoctl RUN apk update --no-cache && \
apk add --upgrade --no-cache libssl3 libcrypto3
RUN rm -rf /var/cache/apk/*
ENTRYPOINT [ "/usr/bin/falcoctl" ] ARG BIN_NAME="falcoctl"
COPY --from=builder /tmp/builder/${BIN_NAME} /usr/bin/${BIN_NAME}
RUN ln -s /usr/bin/${BIN_NAME} /usr/bin/falcoctl-bin
ENTRYPOINT [ "/usr/bin/falcoctl-bin" ]

View File

@ -99,7 +99,7 @@ func (o *artifactInfoOptions) RunArtifactInfo(ctx context.Context, args []string
return err return err
} }
joinedTags := strings.Join(filterOutSigTags(tags), ", ") joinedTags := strings.Join(tags, ", ")
data = append(data, []string{ref, joinedTags}) data = append(data, []string{ref, joinedTags})
} }
@ -110,14 +110,3 @@ func (o *artifactInfoOptions) RunArtifactInfo(ctx context.Context, args []string
return nil return nil
} }
func filterOutSigTags(tags []string) []string {
// Iterate the slice in reverse to avoid index shifting when deleting
for i := len(tags) - 1; i >= 0; i-- {
if strings.HasSuffix(tags[i], ".sig") {
// Remove the element at index i by slicing the slice
tags = append(tags[:i], tags[i+1:]...)
}
}
return tags
}

View File

@ -19,9 +19,6 @@ const (
// FlagAllowedTypes is the name of the flag to specify allowed artifact types. // FlagAllowedTypes is the name of the flag to specify allowed artifact types.
FlagAllowedTypes = "allowed-types" FlagAllowedTypes = "allowed-types"
// FlagPlatform is the name of the flag to override the platform.
FlagPlatform = "platform"
// FlagResolveDeps is the name of the flag to enable artifact dependencies resolution. // FlagResolveDeps is the name of the flag to enable artifact dependencies resolution.
FlagResolveDeps = "resolve-deps" FlagResolveDeps = "resolve-deps"

View File

@ -21,7 +21,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -75,9 +74,6 @@ type artifactInstallOptions struct {
*options.Registry *options.Registry
*options.Directory *options.Directory
allowedTypes oci.ArtifactTypeSlice allowedTypes oci.ArtifactTypeSlice
platform string // Raw string from command line
platformArch string // Architecture portion of parsed platform string
platformOS string // OS portion of parsed platform string
resolveDeps bool resolveDeps bool
noVerify bool noVerify bool
} }
@ -169,15 +165,6 @@ func NewArtifactInstallCmd(ctx context.Context, opt *options.Common) *cobra.Comm
} }
} }
// Parse "platform" into OS and Arch
if len(o.platform) > 0 {
parts := strings.Split(o.platform, "/")
if len(parts) != 2 {
return fmt.Errorf("invalid %q: must be in the format OS/Arch", FlagPlatform)
}
o.platformOS, o.platformArch = parts[0], parts[1]
}
return nil return nil
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
@ -193,8 +180,6 @@ It accepts comma separated values or it can be repeated multiple times.
Examples: Examples:
--%s="rulesfile,plugin" --%s="rulesfile,plugin"
--%s=rulesfile --%s=plugin`, FlagAllowedTypes, FlagAllowedTypes, FlagAllowedTypes)) --%s=rulesfile --%s=plugin`, FlagAllowedTypes, FlagAllowedTypes, FlagAllowedTypes))
cmd.Flags().StringVar(&o.platform, "platform", fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
"os and architecture of the artifact in OS/ARCH format")
cmd.Flags().BoolVar(&o.resolveDeps, FlagResolveDeps, true, cmd.Flags().BoolVar(&o.resolveDeps, FlagResolveDeps, true,
"whether this command should resolve dependencies or not") "whether this command should resolve dependencies or not")
cmd.Flags().BoolVar(&o.noVerify, FlagNoVerify, false, cmd.Flags().BoolVar(&o.noVerify, FlagNoVerify, false,
@ -240,7 +225,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
return nil, err return nil, err
} }
artifactConfig, err := puller.ArtifactConfig(ctx, ref, o.platformOS, o.platformArch) artifactConfig, err := puller.ArtifactConfig(ctx, ref, runtime.GOOS, runtime.GOARCH)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -279,33 +264,31 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
logger.Info("Installing artifacts", logger.Args("refs", refs)) logger.Info("Installing artifacts", logger.Args("refs", refs))
for _, ref := range refs { for _, ref := range refs {
resolvedRef, err := o.IndexCache.ResolveReference(ref) ref, err = o.IndexCache.ResolveReference(ref)
if err != nil { if err != nil {
return err return err
} }
if signatures[resolvedRef] == nil { logger.Info("Preparing to pull artifact", logger.Args("ref", ref))
if sig := o.IndexCache.SignatureForIndexRef(ref); sig != nil {
signatures[resolvedRef] = sig
}
}
logger.Info("Preparing to pull artifact", logger.Args("ref", resolvedRef)) if err := puller.CheckAllowedType(ctx, ref, runtime.GOOS, runtime.GOARCH, o.allowedTypes.Types); err != nil {
if err := puller.CheckAllowedType(ctx, resolvedRef, o.platformOS, o.platformArch, o.allowedTypes.Types); err != nil {
return err return err
} }
// Install will always install artifact for the current OS and architecture // Install will always install artifact for the current OS and architecture
result, err := puller.Pull(ctx, resolvedRef, tmpDir, o.platformOS, o.platformArch) result, err := puller.Pull(ctx, ref, tmpDir, runtime.GOOS, runtime.GOARCH)
if err != nil { if err != nil {
return err return err
} }
sig := signatures[resolvedRef] sig, ok := signatures[ref]
if !ok {
// try to get the signature from the index
sig = o.IndexCache.SignatureForIndexRef(ref)
}
if sig != nil && !o.noVerify { if sig != nil && !o.noVerify {
repo, err := utils.RepositoryFromRef(resolvedRef) repo, err := utils.RepositoryFromRef(ref)
if err != nil { if err != nil {
return err return err
} }
@ -354,7 +337,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
return err return err
} }
// Extract artifact and move it to its destination directory // Extract artifact and move it to its destination directory
_, err = utils.ExtractTarGz(ctx, f, destDir, 0) _, err = utils.ExtractTarGz(f, destDir, 0)
if err != nil { if err != nil {
return fmt.Errorf("cannot extract %q to %q: %w", result.Filename, destDir, err) return fmt.Errorf("cannot extract %q to %q: %w", result.Filename, destDir, err)
} }
@ -367,7 +350,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
if o.Printer.Spinner != nil { if o.Printer.Spinner != nil {
_ = o.Printer.Spinner.Stop() _ = o.Printer.Spinner.Stop()
} }
logger.Info("Artifact successfully installed", logger.Args("name", resolvedRef, "type", result.Type, "digest", result.Digest, "directory", destDir)) logger.Info("Artifact successfully installed", logger.Args("name", ref, "type", result.Type, "digest", result.Digest, "directory", destDir))
} }
return nil return nil

View File

@ -45,7 +45,6 @@ Flags:
--allowed-types=rulesfile --allowed-types=plugin --allowed-types=rulesfile --allowed-types=plugin
-h, --help help for install -h, --help help for install
--plain-http allows interacting with remote registry via plain http requests --plain-http allows interacting with remote registry via plain http requests
--platform string os and architecture of the artifact in OS/ARCH format (default "linux/amd64")
--plugins-dir string directory where to install plugins. (default "/usr/share/falco/plugins") --plugins-dir string directory where to install plugins. (default "/usr/share/falco/plugins")
--resolve-deps whether this command should resolve dependencies or not (default true) --resolve-deps whether this command should resolve dependencies or not (default true)
--rulesfiles-dir string directory where to install rules. (default "/etc/falco") --rulesfiles-dir string directory where to install rules. (default "/etc/falco")
@ -219,7 +218,7 @@ var artifactInstallTests = Describe("install", func() {
Expect(result).ToNot(BeNil()) Expect(result).ToNot(BeNil())
ref = registry + repoAndTag ref = registry + repoAndTag
Expect(err).To(BeNil()) Expect(err).To(BeNil())
args = []string{artifactCmd, installCmd, ref, "--plain-http", "--platform", testPluginPlatform1, args = []string{artifactCmd, installCmd, ref, "--plain-http",
"--config", configFilePath, "--allowed-types", "rulesfile"} "--config", configFilePath, "--allowed-types", "rulesfile"}
}) })
@ -311,7 +310,7 @@ var artifactInstallTests = Describe("install", func() {
Expect(result).ToNot(BeNil()) Expect(result).ToNot(BeNil())
ref = registry + repoAndTag ref = registry + repoAndTag
Expect(err).To(BeNil()) Expect(err).To(BeNil())
args = []string{artifactCmd, installCmd, ref, "--plain-http", "--platform", testPluginPlatform1, args = []string{artifactCmd, installCmd, ref, "--plain-http",
"--config", configFilePath, "--plugins-dir", destDir} "--config", configFilePath, "--plugins-dir", destDir}
}) })
@ -349,7 +348,7 @@ var artifactInstallTests = Describe("install", func() {
Expect(result).ToNot(BeNil()) Expect(result).ToNot(BeNil())
ref = registry + repoAndTag ref = registry + repoAndTag
Expect(err).To(BeNil()) Expect(err).To(BeNil())
args = []string{artifactCmd, installCmd, ref, "--plain-http", "--platform", testPluginPlatform1, args = []string{artifactCmd, installCmd, ref, "--plain-http",
"--config", configFilePath, "--plugins-dir", destDir} "--config", configFilePath, "--plugins-dir", destDir}
}) })
@ -438,28 +437,6 @@ var artifactInstallTests = Describe("install", func() {
}) })
}) })
When("not --platform is not of the correct format", func() {
BeforeEach(func() {
destDir = GinkgoT().TempDir()
err = os.Remove(destDir)
Expect(err).To(BeNil())
baseDir := GinkgoT().TempDir()
configFilePath := baseDir + "/config.yaml"
content := []byte(correctIndexConfig)
err := os.WriteFile(configFilePath, content, 0o644)
Expect(err).To(BeNil())
ref = registry + repoAndTag
args = []string{artifactCmd, installCmd, ref, "--config", configFile, "--platform", "this/is/invalid"}
})
It("check that fails and the usage is not printed", func() {
expectedError := `ERROR invalid "platform": must be in the format OS/Arch`
Expect(output).ShouldNot(gbytes.Say(regexp.QuoteMeta(artifactInstallUsage)))
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(expectedError)))
})
})
}) })
}) })

View File

@ -41,8 +41,9 @@ func NewDriverCleanupCmd(ctx context.Context, opt *options.Common, driver *optio
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "cleanup [flags]", Use: "cleanup [flags]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: "Cleanup a driver", Short: "[Preview] Cleanup a driver",
Long: `Cleans a driver up, eg for kmod, by removing it from dkms.`, Long: `[Preview] Cleans a driver up, eg for kmod, by removing it from dkms.
** This command is in preview and under development. **`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return o.RunDriverCleanup(ctx) return o.RunDriverCleanup(ctx)
}, },
@ -65,7 +66,7 @@ func (o *driverCleanupOptions) RunDriverCleanup(_ context.Context) error {
if o.Printer.Logger.Formatter == pterm.LogFormatterJSON { if o.Printer.Logger.Formatter == pterm.LogFormatterJSON {
// Only print formatted text if we are formatting to json // Only print formatted text if we are formatting to json
out := strings.ReplaceAll(buf.String(), "\n", ";") out := strings.ReplaceAll(buf.String(), "\n", ";")
o.Printer.Logger.Info("Driver cleanup", o.Printer.Logger.Args("output", out)) o.Printer.Logger.Info("Driver build", o.Printer.Logger.Args("output", out))
} else { } else {
// Print much more readable output as-is // Print much more readable output as-is
o.Printer.DefaultText.Print(buf.String()) o.Printer.DefaultText.Print(buf.String())

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -25,8 +25,8 @@ import (
"github.com/falcosecurity/falcoctl/cmd" "github.com/falcosecurity/falcoctl/cmd"
) )
//nolint:lll // no need to check for line length. var driverCleanupHelp = `[Preview] Cleans a driver up, eg for kmod, by removing it from dkms.
var driverCleanupHelp = `Cleans a driver up, eg for kmod, by removing it from dkms. ** This command is in preview and under development. **
Usage: Usage:
falcoctl driver cleanup [flags] falcoctl driver cleanup [flags]
@ -37,13 +37,11 @@ Flags:
Global Flags: Global Flags:
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml") --config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
--host-root string Driver host root to be used. (default "/") --host-root string Driver host root to be used. (default "/")
--kernelrelease string Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')
--kernelversion string Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')
--log-format string Set formatting for logs (color, text, json) (default "color") --log-format string Set formatting for logs (color, text, json) (default "color")
--log-level string Set level for logs (info, warn, debug, trace) (default "info") --log-level string Set level for logs (info, warn, debug, trace) (default "info")
--name string Driver name to be used. (default "falco") --name string Driver name to be used. (default "falco")
--repo strings Driver repo to be used. (default [https://download.falco.org/driver]) --repo strings Driver repo to be used. (default [https://download.falco.org/driver])
--type strings Driver types allowed in descending priority order (ebpf, kmod, modern_ebpf) (default [modern_ebpf,kmod,ebpf]) --type string Driver type to be used (auto, ebpf, kmod, modern_ebpf) (default "kmod")
--version string Driver version to be used. --version string Driver version to be used.
` `
@ -95,7 +93,8 @@ var _ = Describe("cleanup", func() {
BeforeEach(func() { BeforeEach(func() {
args = []string{driverCmd, cleanupCmd, "--config", configFile, "--type", "foo"} args = []string{driverCmd, cleanupCmd, "--config", configFile, "--type", "foo"}
}) })
addAssertFailedBehavior(`ERROR unsupported driver type specified: foo`) addAssertFailedBehavior(`ERROR invalid argument "foo" for "--type" flag: invalid argument "foo",` +
` please provide one of (auto, ebpf, kmod, modern_ebpf)`)
}) })
}) })
}) })

View File

@ -1,326 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package driverconfig
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/falcosecurity/driverkit/pkg/kernelrelease"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type"
"github.com/falcosecurity/falcoctl/pkg/options"
)
const (
falcoName = "falco"
)
func newOptions() *driverConfigOptions {
common := options.NewOptions()
common.Initialize()
// Parse the driver type.
dType, _ := drivertype.Parse("modern_ebpf")
return &driverConfigOptions{
Common: common,
Driver: &options.Driver{
Type: dType,
Name: falcoName,
Repos: []string{"https://download.falco.org/driver"},
Version: "6.0.0+driver",
HostRoot: "/",
Distro: nil,
Kr: kernelrelease.KernelRelease{},
},
update: false,
namespace: "",
kubeconfig: "",
configmap: "",
configDir: "",
}
}
func createFalcoConfigFile(cfg falcoCfg, configDir string) error {
engineKind, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("unable to marshal falco config: %w", err)
}
// Write the engine configuration to a specialized config file.
if err := os.WriteFile(filepath.Join(configDir, "falco.yaml"), engineKind, 0o600); err != nil {
return fmt.Errorf("unable to write falco.yaml file: %w", err)
}
return nil
}
func createFalcoConfigMap(cfg falcoCfg, dataKey string) (*v1.ConfigMap, error) {
engineKind, err := yaml.Marshal(cfg)
if err != nil {
return nil, fmt.Errorf("unable to marshal falco config: %w", err)
}
cm := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: falcoName,
Namespace: falcoName,
},
Data: map[string]string{
dataKey: string(engineKind),
},
}
return cm, nil
}
func TestDriverConfigOptions_Commit_Host(t *testing.T) {
testCases := []struct {
name string
args func(t *testing.T) *driverConfigOptions
expected func(t *testing.T, opt *driverConfigOptions, err error)
}{
{
"no falco config file",
func(t *testing.T) *driverConfigOptions {
opt := newOptions()
opt.configDir = "no-file-at-all"
opt.update = true
return opt
},
func(t *testing.T, opt *driverConfigOptions, err error) {
require.Error(t, err, "should error since falco configuration file does not exist")
require.ErrorContains(t, err, "open no-file-at-all/falco.yaml: no such file or directory")
},
},
{
"update-falco-config",
func(t *testing.T) *driverConfigOptions {
opt := newOptions()
dir, err := os.MkdirTemp("", "falcoctl-driver-config-test")
require.NoError(t, err)
// Write falco configuration file.
cfg := falcoCfg{engineCfg{Kind: "modern_ebpf"}}
err = createFalcoConfigFile(cfg, dir)
require.NoError(t, err)
opt.configDir = dir
return opt
},
func(t *testing.T, opt *driverConfigOptions, err error) {
require.NoError(t, err, "should not error")
// Config file.
specCfgFile := filepath.Join(opt.configDir, "config.d", falcoDriverConfigFile)
// Check that config file has been created.
_, err = os.Stat(specCfgFile)
require.NoError(t, err)
content, err := os.ReadFile(specCfgFile)
require.NoError(t, err)
cfg := falcoCfg{}
err = yaml.Unmarshal(content, &cfg)
require.NoError(t, err)
require.Equal(t, opt.Type.String(), cfg.Engine.Kind)
},
},
{
"falco-not-in-driver-mode",
func(t *testing.T) *driverConfigOptions {
opt := newOptions()
dir, err := os.MkdirTemp("", "falcoctl-driver-config-test")
require.NoError(t, err)
// Write falco configuration file.
cfg := falcoCfg{engineCfg{Kind: "nodriver"}}
err = createFalcoConfigFile(cfg, dir)
require.NoError(t, err)
opt.configDir = dir
return opt
},
func(t *testing.T, opt *driverConfigOptions, err error) {
require.NoError(t, err, "should not error")
// Config file.
specCfgFile := filepath.Join(opt.configDir, "config.d", falcoDriverConfigFile)
// Check that config file has been created.
_, err = os.Stat(specCfgFile)
require.True(t, os.IsNotExist(err))
},
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
opt := testCase.args(t)
err := opt.Commit(context.Background(), nil, opt.Type)
testCase.expected(t, opt, err)
})
}
}
func TestDriverConfigOptions_Commit_K8S(t *testing.T) {
testCases := []struct {
name string
args func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap)
expected func(t *testing.T, opt *driverConfigOptions, err error)
}{
{
"no falco configmap, wrong namespace",
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
opt := newOptions()
opt.namespace = "wrong-namespace"
opt.configmap = falcoName
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "modern_ebpf"}}, "falco.yaml")
require.NoError(t, err)
return opt, cm
},
func(t *testing.T, opt *driverConfigOptions, err error) {
require.Error(t, err, "should error since falco configmap does not exist")
require.ErrorContains(t, err, "unable to get configmap falco in namespace wrong-namespace")
},
},
{
"no falco configmap, wrong name",
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
opt := newOptions()
opt.namespace = falcoName
opt.configmap = "wrong-name"
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "modern_ebpf"}}, "falco.yaml")
require.NoError(t, err)
return opt, cm
},
func(t *testing.T, opt *driverConfigOptions, err error) {
require.Error(t, err, "should error since falco configmap does not exist")
require.ErrorContains(t, err, "unable to get configmap wrong-name in namespace falco")
},
},
{
"no falco config, wrong data key",
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
opt := newOptions()
opt.namespace = falcoName
opt.configmap = falcoName
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "modern_ebpf"}}, "wrong-data-key")
require.NoError(t, err)
return opt, cm
},
func(t *testing.T, opt *driverConfigOptions, err error) {
require.Error(t, err, "should error since falco configmap does not exist")
require.ErrorContains(t, err, "configMap falco does not contain key \"falco.yaml\"")
},
},
{
"update-falco-config",
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
opt := newOptions()
opt.namespace = falcoName
opt.configmap = falcoName
dir, err := os.MkdirTemp("", "falcoctl-driver-config-test")
require.NoError(t, err)
opt.configDir = dir
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "modern_ebpf"}}, "falco.yaml")
require.NoError(t, err)
return opt, cm
},
func(t *testing.T, opt *driverConfigOptions, err error) {
require.NoError(t, err, "should not error")
// Config file.
specCfgFile := filepath.Join(opt.configDir, "config.d", falcoDriverConfigFile)
// Check that config file has been created.
_, err = os.Stat(specCfgFile)
require.NoError(t, err)
content, err := os.ReadFile(specCfgFile)
require.NoError(t, err)
cfg := falcoCfg{}
err = yaml.Unmarshal(content, &cfg)
require.NoError(t, err)
require.Equal(t, opt.Type.String(), cfg.Engine.Kind)
},
},
{
"falco-not-in-driver-mode",
func(t *testing.T) (*driverConfigOptions, *v1.ConfigMap) {
opt := newOptions()
opt.namespace = falcoName
opt.configmap = falcoName
dir, err := os.MkdirTemp("", "falcoctl-driver-config-test")
require.NoError(t, err)
cm, err := createFalcoConfigMap(falcoCfg{engineCfg{Kind: "nodriver"}}, "falco.yaml")
require.NoError(t, err)
opt.configDir = dir
return opt, cm
},
func(t *testing.T, opt *driverConfigOptions, err error) {
require.NoError(t, err, "should not error")
// Config file.
specCfgFile := filepath.Join(opt.configDir, "config.d", falcoDriverConfigFile)
// Check that config file has been created.
_, err = os.Stat(specCfgFile)
require.True(t, os.IsNotExist(err))
},
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
opt, cm := testCase.args(t)
// Create fake client.
fakeClient := fake.NewSimpleClientset(cm)
err := opt.Commit(context.Background(), fakeClient, opt.Type)
testCase.expected(t, opt, err)
})
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -16,50 +16,44 @@
package driverconfig package driverconfig
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/net/context" "golang.org/x/net/context"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"github.com/falcosecurity/falcoctl/internal/config" "github.com/falcosecurity/falcoctl/internal/config"
"github.com/falcosecurity/falcoctl/internal/utils"
drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type"
"github.com/falcosecurity/falcoctl/pkg/options" "github.com/falcosecurity/falcoctl/pkg/options"
) )
const ( const (
longConfig = `Configure a driver for future usages with other driver subcommands. configMapEngineKindKey = "engine.kind"
longConfig = `[Preview] Configure a driver for future usages with other driver subcommands.
It will also update local Falco configuration or k8s configmap depending on the environment where it is running, to let Falco use chosen driver. It will also update local Falco configuration or k8s configmap depending on the environment where it is running, to let Falco use chosen driver.
Only supports deployments of Falco that use a driver engine, ie: one between kmod, ebpf and modern-ebpf. Only supports deployments of Falco that use a driver engine, ie: one between kmod, ebpf and modern-ebpf.
If engine.kind key is set to a non-driver driven engine, Falco configuration won't be touched. If engine.kind key is set to a non-driver driven engine, Falco configuration won't be touched.
** This command is in preview and under development. **
` `
falcoConfigFile = "falco.yaml"
falcoDriverConfigFile = "engine-kind-falcoctl.yaml"
) )
type driverConfigOptions struct { type driverConfigOptions struct {
*options.Common *options.Common
*options.Driver *options.Driver
update bool Update bool
namespace string Namespace string
kubeconfig string KubeConfig string
configmap string
configDir string
}
type engineCfg struct {
Kind string `yaml:"kind"`
}
type falcoCfg struct {
Engine engineCfg `yaml:"engine"`
} }
// NewDriverConfigCmd configures a driver and stores it in config. // NewDriverConfigCmd configures a driver and stores it in config.
@ -72,36 +66,16 @@ func NewDriverConfigCmd(ctx context.Context, opt *options.Common, driver *option
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "config [flags]", Use: "config [flags]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: "Configure a driver", Short: "[Preview] Configure a driver",
Long: longConfig, Long: longConfig,
PreRunE: func(cmd *cobra.Command, args []string) error {
viper.AutomaticEnv()
_ = viper.BindPFlag("driver.config.configmap", cmd.Flags().Lookup("configmap"))
_ = viper.BindPFlag("driver.config.namespace", cmd.Flags().Lookup("namespace"))
_ = viper.BindPFlag("driver.config.update_falco", cmd.Flags().Lookup("update-falco"))
_ = viper.BindPFlag("driver.config.kubeconfig", cmd.Flags().Lookup("kubeconfig"))
_ = viper.BindPFlag("driver.config.configdir", cmd.Flags().Lookup("falco-config-dir"))
o.configmap = viper.GetString("driver.config.configmap")
o.namespace = viper.GetString("driver.config.namespace")
o.kubeconfig = viper.GetString("driver.config.kubeconfig")
o.update = viper.GetBool("driver.config.update_falco")
o.configDir = viper.GetString("driver.config.configdir")
return nil
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return o.RunDriverConfig(ctx) return o.RunDriverConfig(ctx)
}, },
} }
cmd.Flags().BoolVar(&o.update, "update-falco", true, "Whether to overwrite Falco configuration") cmd.Flags().BoolVar(&o.Update, "update-falco", true, "Whether to update Falco config/configmap.")
cmd.Flags().StringVar(&o.namespace, "namespace", "", "Kubernetes namespace.") cmd.Flags().StringVar(&o.Namespace, "namespace", "", "Kubernetes namespace.")
cmd.Flags().StringVar(&o.kubeconfig, "kubeconfig", "", "Kubernetes config.") cmd.Flags().StringVar(&o.KubeConfig, "kubeconfig", "", "Kubernetes config.")
cmd.Flags().StringVar(&o.configmap, "configmap", "", "Falco configmap name.")
cmd.Flags().StringVar(&o.configDir, "falco-config-dir", "/etc/falco", "Falco configuration directory.")
return cmd return cmd
} }
@ -114,149 +88,115 @@ func (o *driverConfigOptions) RunDriverConfig(ctx context.Context) error {
"host-root", o.Driver.HostRoot, "host-root", o.Driver.HostRoot,
"repos", strings.Join(o.Driver.Repos, ","))) "repos", strings.Join(o.Driver.Repos, ",")))
if o.update { if o.Update {
var cl kubernetes.Interface if err := o.commit(ctx, o.Driver.Type); err != nil {
var err error
if o.namespace != "" {
// Create a new clientset.
if cl, err = setupClient(o.kubeconfig); err != nil {
return err return err
} }
} }
if err := o.Commit(ctx, cl, o.Driver.Type); err != nil {
return err
}
}
o.Printer.Logger.Info("Storing falcoctl driver config")
return config.StoreDriver(o.Driver.ToDriverConfig(), o.ConfigFile) return config.StoreDriver(o.Driver.ToDriverConfig(), o.ConfigFile)
} }
func checkFalcoRunsWithDrivers(engineKind string) bool { func checkFalcoRunsWithDrivers(engineKind string) error {
// Modify the data in the ConfigMap/Falco config file ONLY if engine.kind is set to a known driver type. // Modify the data in the ConfigMap/Falco config file ONLY if engine.kind is set to a known driver type.
// This ensures that we modify the config only for Falcos running with drivers, and not plugins/gvisor. // This ensures that we modify the config only for Falcos running with drivers, and not plugins/gvisor.
// Scenario: user has multiple Falco pods deployed in its cluster, one running with driver, // Scenario: user has multiple Falco pods deployed in its cluster, one running with driver,
// other running with plugins. We must only touch the one running with driver. // other running with plugins. We must only touch the one running with driver.
if _, err := drivertype.Parse(engineKind); err != nil { if _, err := drivertype.Parse(engineKind); err != nil {
return false return fmt.Errorf("engine.kind is not driver driven: %s", engineKind)
} }
return true
}
func (o *driverConfigOptions) IsRunningInDriverModeHost() (bool, error) {
o.Printer.Logger.Debug("Checking if Falco is running in driver mode on host system")
falcoCfgFile := filepath.Join(o.configDir, falcoConfigFile)
yamlFile, err := os.ReadFile(filepath.Clean(falcoCfgFile))
if err != nil {
return false, err
}
cfg := falcoCfg{}
if err = yaml.Unmarshal(yamlFile, &cfg); err != nil {
return false, fmt.Errorf("unable to unmarshal falco.yaml to falcoCfg struct: %w", err)
}
return checkFalcoRunsWithDrivers(cfg.Engine.Kind), nil
}
func (o *driverConfigOptions) IsRunningInDriverModeK8S(ctx context.Context, cl kubernetes.Interface) (bool, error) {
o.Printer.Logger.Debug("Checking if Falco is running in driver mode in Kubernetes")
configMap, err := cl.CoreV1().ConfigMaps(o.namespace).Get(ctx, o.configmap, metav1.GetOptions{})
if err != nil {
return false, fmt.Errorf("unable to get configmap %s in namespace %s: %w", o.configmap, o.namespace, err)
}
// Check that this is a Falco config map
falcoYaml, present := configMap.Data["falco.yaml"]
if !present {
o.Printer.Logger.Debug("Skip non Falco-related config map",
o.Printer.Logger.Args("configMap", configMap.Name))
return false, fmt.Errorf("configMap %s does not contain key \"falco.yaml\"", o.configmap)
}
// Check that Falco is configured to run with a driver
var falcoConfig falcoCfg
err = yaml.Unmarshal([]byte(falcoYaml), &falcoConfig)
if err != nil {
return false, fmt.Errorf("unable to unmarshal falco.yaml to falcoCfg struct: %w", err)
}
return checkFalcoRunsWithDrivers(falcoConfig.Engine.Kind), nil
}
// Commit saves the updated driver type to Falco config,
// in a specialized configuration file under /etc/falco/config.d.
func (o *driverConfigOptions) Commit(ctx context.Context, cl kubernetes.Interface, driverType drivertype.DriverType) error {
// If set to true, then we need to overwrite the driver type.
var overwrite bool
var err error
if cl != nil {
if overwrite, err = o.IsRunningInDriverModeK8S(ctx, cl); err != nil {
return err
}
} else {
if overwrite, err = o.IsRunningInDriverModeHost(); err != nil {
return err
}
}
if overwrite {
o.Printer.Logger.Info("Committing driver config to specialized configuration file under",
o.Printer.Logger.Args("directory", filepath.Join(o.configDir, "config.d")))
return overwriteDriverType(o.configDir, driverType)
}
o.Printer.Logger.Info("Falco is not configured to run with a driver, no need to set driver type.")
return nil return nil
} }
func setupClient(kubeconfig string) (kubernetes.Interface, error) { func (o *driverConfigOptions) replaceDriverTypeInFalcoConfig(driverType drivertype.DriverType) error {
var cfg *rest.Config falcoCfgFile := filepath.Clean(filepath.Join(string(os.PathSeparator), "etc", "falco", "falco.yaml"))
var err error type engineCfg struct {
Kind string `yaml:"kind"`
}
type falcoCfg struct {
Engine engineCfg `yaml:"engine"`
}
yamlFile, err := os.ReadFile(filepath.Clean(falcoCfgFile))
if err != nil {
return err
}
cfg := falcoCfg{}
if err = yaml.Unmarshal(yamlFile, &cfg); err != nil {
return err
}
if err = checkFalcoRunsWithDrivers(cfg.Engine.Kind); err != nil {
o.Printer.Logger.Warn("Avoid updating Falco configuration",
o.Printer.Logger.Args("config", falcoCfgFile, "reason", err))
return nil
}
const configKindKey = "kind: "
return utils.ReplaceTextInFile(falcoCfgFile, configKindKey+cfg.Engine.Kind, configKindKey+driverType.String(), 1)
}
// Create the rest config. func (o *driverConfigOptions) replaceDriverTypeInK8SConfigMap(ctx context.Context, driverType drivertype.DriverType) error {
if kubeconfig != "" { var (
cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig) err error
cfg *rest.Config
)
if o.KubeConfig != "" {
cfg, err = clientcmd.BuildConfigFromFlags("", o.KubeConfig)
} else { } else {
cfg, err = rest.InClusterConfig() cfg, err = rest.InClusterConfig()
} }
if err != nil { if err != nil {
return nil, err
}
// Create the clientset.
return kubernetes.NewForConfig(cfg)
}
func overwriteDriverType(configDir string, driverType drivertype.DriverType) error {
var falcoConfig falcoCfg
configDir = filepath.Join(configDir, "config.d")
// First thing, check if config.d folder exists in the configuration directory.
_, err := os.Stat(configDir)
if os.IsNotExist(err) {
// Create it.
// #nosec G301 -- under /etc we want 755 permissions
if err := os.MkdirAll(configDir, 0o755); err != nil {
return fmt.Errorf("unable to create directory %s: %w", configDir, err)
}
} else if err != nil && !os.IsNotExist(err) {
return err return err
} }
falcoConfig.Engine.Kind = driverType.String() cl, err := kubernetes.NewForConfig(cfg)
engineKind, err := yaml.Marshal(falcoConfig)
if err != nil { if err != nil {
return fmt.Errorf("unable to marshal falco config: %w", err) return err
} }
// Write the engine configuration to a specialized config file. configMapList, err := cl.CoreV1().ConfigMaps(o.Namespace).List(ctx, metav1.ListOptions{
// #nosec G306 //under /etc we want 644 permissions LabelSelector: "app.kubernetes.io/instance: falco",
if err := os.WriteFile(filepath.Join(configDir, falcoDriverConfigFile), engineKind, 0o644); err != nil { })
return fmt.Errorf("unable to persist engine kind to filesystem: %w", err) if err != nil {
return err
}
if configMapList.Size() == 0 {
return errors.New(`no configmaps matching "app.kubernetes.io/instance: falco" label were found`)
} }
type patchDriverTypeValue struct {
Op string `json:"op"`
Path string `json:"path"`
Value string `json:"value"`
}
payload := []patchDriverTypeValue{{
Op: "replace",
Path: "/data/" + configMapEngineKindKey,
Value: driverType.String(),
}}
plBytes, _ := json.Marshal(payload)
for i := 0; i < configMapList.Size(); i++ {
configMap := configMapList.Items[i]
currEngineKind := configMap.Data[configMapEngineKindKey]
if err = checkFalcoRunsWithDrivers(currEngineKind); err != nil {
o.Printer.Logger.Warn("Avoid updating Falco configMap",
o.Printer.Logger.Args("configMap", configMap.Name, "reason", err))
continue
}
// Patch the configMap
if _, err = cl.CoreV1().ConfigMaps(configMap.Namespace).Patch(
ctx, configMap.Name, types.JSONPatchType, plBytes, metav1.PatchOptions{}); err != nil {
return err
}
}
return nil return nil
} }
// commit saves the updated driver type to Falco config,
// either to the local falco.yaml or updating the deployment configmap.
func (o *driverConfigOptions) commit(ctx context.Context, driverType drivertype.DriverType) error {
if o.Namespace != "" {
// Ok we are on k8s
return o.replaceDriverTypeInK8SConfigMap(ctx, driverType)
}
return o.replaceDriverTypeInFalcoConfig(driverType)
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -25,33 +25,29 @@ import (
"github.com/falcosecurity/falcoctl/cmd" "github.com/falcosecurity/falcoctl/cmd"
) )
//nolint:lll // no need to check for line length. var driverConfigHelp = `[Preview] Configure a driver for future usages with other driver subcommands.
var driverConfigHelp = `Configure a driver for future usages with other driver subcommands.
It will also update local Falco configuration or k8s configmap depending on the environment where it is running, to let Falco use chosen driver. It will also update local Falco configuration or k8s configmap depending on the environment where it is running, to let Falco use chosen driver.
Only supports deployments of Falco that use a driver engine, ie: one between kmod, ebpf and modern-ebpf. Only supports deployments of Falco that use a driver engine, ie: one between kmod, ebpf and modern-ebpf.
If engine.kind key is set to a non-driver driven engine, Falco configuration won't be touched. If engine.kind key is set to a non-driver driven engine, Falco configuration won't be touched.
** This command is in preview and under development. **
Usage: Usage:
falcoctl driver config [flags] falcoctl driver config [flags]
Flags: Flags:
--configmap string Falco configmap name.
--falco-config-dir string Falco configuration directory. (default "/etc/falco")
-h, --help help for config -h, --help help for config
--kubeconfig string Kubernetes config. --kubeconfig string Kubernetes config.
--namespace string Kubernetes namespace. --namespace string Kubernetes namespace.
--update-falco Whether to overwrite Falco configuration (default true) --update-falco Whether to update Falco config/configmap. (default true)
Global Flags: Global Flags:
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml") --config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
--host-root string Driver host root to be used. (default "/") --host-root string Driver host root to be used. (default "/")
--kernelrelease string Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')
--kernelversion string Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')
--log-format string Set formatting for logs (color, text, json) (default "color") --log-format string Set formatting for logs (color, text, json) (default "color")
--log-level string Set level for logs (info, warn, debug, trace) (default "info") --log-level string Set level for logs (info, warn, debug, trace) (default "info")
--name string Driver name to be used. (default "falco") --name string Driver name to be used. (default "falco")
--repo strings Driver repo to be used. (default [https://download.falco.org/driver]) --repo strings Driver repo to be used. (default [https://download.falco.org/driver])
--type strings Driver types allowed in descending priority order (ebpf, kmod, modern_ebpf) (default [modern_ebpf,kmod,ebpf]) --type string Driver type to be used (auto, ebpf, kmod, modern_ebpf) (default "kmod")
--version string Driver version to be used. --version string Driver version to be used.
` `
@ -103,7 +99,8 @@ var _ = Describe("config", func() {
BeforeEach(func() { BeforeEach(func() {
args = []string{driverCmd, configCmd, "--config", configFile, "--type", "foo"} args = []string{driverCmd, configCmd, "--config", configFile, "--type", "foo"}
}) })
addAssertFailedBehavior(`ERROR unsupported driver type specified: foo`) addAssertFailedBehavior(`ERROR invalid argument "foo" for "--type" flag: invalid argument "foo",` +
` please provide one of (auto, ebpf, kmod, modern_ebpf)`)
}) })
}) })
}) })

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -44,18 +44,14 @@ import (
// NewDriverCmd returns the driver command. // NewDriverCmd returns the driver command.
func NewDriverCmd(ctx context.Context, opt *options.Common) *cobra.Command { func NewDriverCmd(ctx context.Context, opt *options.Common) *cobra.Command {
driver := &options.Driver{} driver := &options.Driver{}
driverTypesEnum := options.NewDriverTypes() driverTypes := options.NewDriverTypes()
var (
driverTypesStr []string
driverKernelRelease string
driverKernelVersion string
)
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "driver", Use: "driver",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: "Interact with falcosecurity driver", Short: "[Preview] Interact with falcosecurity driver",
Long: `Interact with falcosecurity driver.`, Long: `[Preview] Interact with falcosecurity driver.
** This command is in preview and under development. **`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
opt.Initialize() opt.Initialize()
if err := config.Load(opt.ConfigFile); err != nil { if err := config.Load(opt.ConfigFile); err != nil {
@ -119,62 +115,44 @@ func NewDriverCmd(ctx context.Context, opt *options.Common) *cobra.Command {
// should never happen // should never happen
return fmt.Errorf("unable to retrieve flag type") return fmt.Errorf("unable to retrieve flag type")
} else if !f.Changed && viper.IsSet(config.DriverTypeKey) { } else if !f.Changed && viper.IsSet(config.DriverTypeKey) {
val, err := config.DriverTypes() val := viper.Get(config.DriverTypeKey)
if err != nil { if err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)); err != nil {
return err
}
if err := cmd.Flags().Set(f.Name, strings.Join(val, ",")); err != nil {
return fmt.Errorf("unable to overwrite \"type\" flag: %w", err) return fmt.Errorf("unable to overwrite \"type\" flag: %w", err)
} }
} }
// Logic to discover correct driver to be used if driverTypes.String() != drivertype.TypeAuto {
// Step 1: build up allowed driver types var err error
allowedDriverTypes := make([]drivertype.DriverType, 0)
for _, dTypeStr := range driverTypesStr {
// Ok driver type was enforced by the user // Ok driver type was enforced by the user
drvType, err := drivertype.Parse(dTypeStr) driver.Type, err = drivertype.Parse(driverTypes.String())
if err != nil { if err != nil {
return err return err
} }
allowedDriverTypes = append(allowedDriverTypes, drvType) } else {
opt.Printer.Logger.Debug("Allowed driver", // automatic logic
opt.Printer.Logger.Args("type", drvType)) info, err := driverkernel.FetchInfo("", "")
}
// Step 2: fetch system info (kernel release/version and distro)
var err error
driver.Kr, err = driverkernel.FetchInfo(driverKernelRelease, driverKernelVersion)
if err != nil { if err != nil {
return err return err
} }
opt.Printer.Logger.Debug("Fetched kernel info", opt.Printer.Logger.Args( opt.Printer.Logger.Debug("Fetched kernel info", opt.Printer.Logger.Args(
"arch", driver.Kr.Architecture.ToNonDeb(), "arch", info.Architecture.ToNonDeb(),
"kernel release", driver.Kr.String(), "kernel release", info.String(),
"kernel version", driver.Kr.KernelVersion)) "kernel version", info.KernelVersion))
driver.Distro, err = driverdistro.Discover(driver.Kr, driver.HostRoot) d, err := driverdistro.Discover(info, driver.HostRoot)
if err != nil { if err != nil {
if !errors.Is(err, driverdistro.ErrUnsupported) { if !errors.Is(err, driverdistro.ErrUnsupported) {
return err return err
} }
opt.Printer.Logger.Debug("Detected an unsupported target system; falling back at generic logic.") opt.Printer.Logger.Info("Detected an unsupported target system; falling back at generic logic.")
} }
opt.Printer.Logger.Debug("Discovered distro", opt.Printer.Logger.Args("target", driver.Distro)) opt.Printer.Logger.Debug("Discovered distro", opt.Printer.Logger.Args("target", d))
driver.Type = driver.Distro.PreferredDriver(driver.Kr, allowedDriverTypes) driver.Type = d.PreferredDriver(info)
if driver.Type == nil { if driver.Type == nil {
return fmt.Errorf("no supported driver found for distro: %s, "+ return fmt.Errorf("automatic driver selection failed")
"kernelrelease %s, "+ }
"kernelversion %s, "+
"arch %s",
driver.Distro.String(),
driver.Kr.String(),
driver.Kr.KernelVersion,
driver.Kr.Architecture.ToNonDeb())
} }
opt.Printer.Logger.Debug("Detected supported driver", opt.Printer.Logger.Args("type", driver.Type.String()))
// If empty, try to load it automatically from /usr/src sub folders, // If empty, try to load it automatically from /usr/src sub folders,
// using the most recent (ie: the one with greatest semver) driver version. // using the most recent (ie: the one with greatest semver) driver version.
if driver.Version == "" { if driver.Version == "" {
@ -184,22 +162,11 @@ func NewDriverCmd(ctx context.Context, opt *options.Common) *cobra.Command {
}, },
} }
cmd.PersistentFlags().StringSliceVar(&driverTypesStr, "type", config.DefaultDriver.Type, cmd.PersistentFlags().Var(driverTypes, "type", "Driver type to be used "+driverTypes.Allowed())
"Driver types allowed in descending priority order "+driverTypesEnum.Allowed())
cmd.PersistentFlags().StringVar(&driver.Version, "version", config.DefaultDriver.Version, "Driver version to be used.") cmd.PersistentFlags().StringVar(&driver.Version, "version", config.DefaultDriver.Version, "Driver version to be used.")
cmd.PersistentFlags().StringSliceVar(&driver.Repos, "repo", config.DefaultDriver.Repos, "Driver repo to be used.") cmd.PersistentFlags().StringSliceVar(&driver.Repos, "repo", config.DefaultDriver.Repos, "Driver repo to be used.")
cmd.PersistentFlags().StringVar(&driver.Name, "name", config.DefaultDriver.Name, "Driver name to be used.") cmd.PersistentFlags().StringVar(&driver.Name, "name", config.DefaultDriver.Name, "Driver name to be used.")
cmd.PersistentFlags().StringVar(&driver.HostRoot, "host-root", config.DefaultDriver.HostRoot, "Driver host root to be used.") cmd.PersistentFlags().StringVar(&driver.HostRoot, "host-root", config.DefaultDriver.HostRoot, "Driver host root to be used.")
cmd.PersistentFlags().StringVar(&driverKernelRelease,
"kernelrelease",
"",
"Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' "+
"(e.g. '6.1.0-10-cloud-amd64')")
cmd.PersistentFlags().StringVar(&driverKernelVersion,
"kernelversion",
"",
"Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' "+
"(e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')")
cmd.AddCommand(driverinstall.NewDriverInstallCmd(ctx, opt, driver)) cmd.AddCommand(driverinstall.NewDriverInstallCmd(ctx, opt, driver))
cmd.AddCommand(driverconfig.NewDriverConfigCmd(ctx, opt, driver)) cmd.AddCommand(driverconfig.NewDriverConfigCmd(ctx, opt, driver))

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -29,13 +29,13 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
driverdistro "github.com/falcosecurity/falcoctl/pkg/driver/distro" driverdistro "github.com/falcosecurity/falcoctl/pkg/driver/distro"
driverkernel "github.com/falcosecurity/falcoctl/pkg/driver/kernel"
"github.com/falcosecurity/falcoctl/pkg/options" "github.com/falcosecurity/falcoctl/pkg/options"
) )
type driverDownloadOptions struct { type driverDownloadOptions struct {
InsecureDownload bool InsecureDownload bool
HTTPTimeout time.Duration HTTPTimeout time.Duration
HTTPHeaders string
} }
type driverInstallOptions struct { type driverInstallOptions struct {
@ -43,7 +43,8 @@ type driverInstallOptions struct {
*options.Driver *options.Driver
Download bool Download bool
Compile bool Compile bool
DownloadHeaders bool DriverKernelRelease string
DriverKernelVersion string
driverDownloadOptions driverDownloadOptions
} }
@ -60,8 +61,9 @@ func NewDriverInstallCmd(ctx context.Context, opt *options.Common, driver *optio
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "install [flags]", Use: "install [flags]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: "Install previously configured driver", Short: "[Preview] Install previously configured driver",
Long: `Install previously configured driver, either downloading it or attempting a build.`, Long: `[Preview] Install previously configured driver, either downloading it or attempting a build.
** This command is in preview and under development. **`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
dest, err := o.RunDriverInstall(ctx) dest, err := o.RunDriverInstall(ctx)
if dest != "" { if dest != "" {
@ -78,13 +80,18 @@ func NewDriverInstallCmd(ctx context.Context, opt *options.Common, driver *optio
cmd.Flags().BoolVar(&o.Download, "download", true, "Whether to enable download of prebuilt drivers") cmd.Flags().BoolVar(&o.Download, "download", true, "Whether to enable download of prebuilt drivers")
cmd.Flags().BoolVar(&o.Compile, "compile", true, "Whether to enable local compilation of drivers") cmd.Flags().BoolVar(&o.Compile, "compile", true, "Whether to enable local compilation of drivers")
cmd.Flags().BoolVar(&o.DownloadHeaders, "download-headers", true, "Whether to enable automatic kernel headers download where supported") cmd.Flags().StringVar(&o.DriverKernelRelease,
"kernelrelease",
"",
"Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' "+
"(e.g. '6.1.0-10-cloud-amd64')")
cmd.Flags().StringVar(&o.DriverKernelVersion,
"kernelversion",
"",
"Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' "+
"(e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')")
cmd.Flags().BoolVar(&o.InsecureDownload, "http-insecure", false, "Whether you want to allow insecure downloads or not") cmd.Flags().BoolVar(&o.InsecureDownload, "http-insecure", false, "Whether you want to allow insecure downloads or not")
cmd.Flags().DurationVar(&o.HTTPTimeout, "http-timeout", 60*time.Second, "Timeout for each http try") cmd.Flags().DurationVar(&o.HTTPTimeout, "http-timeout", 60*time.Second, "Timeout for each http try")
cmd.Flags().StringVar(&o.HTTPHeaders, "http-headers",
"",
"Optional comma-separated list of headers for the http GET request "+
"(e.g. --http-headers='x-emc-namespace: default,Proxy-Authenticate: Basic'). Not necessary if default repo is used")
return cmd return cmd
} }
@ -99,16 +106,20 @@ func setDefaultHTTPClientOpts(downloadOptions driverDownloadOptions) {
// RunDriverInstall implements the driver install command. // RunDriverInstall implements the driver install command.
func (o *driverInstallOptions) RunDriverInstall(ctx context.Context) (string, error) { func (o *driverInstallOptions) RunDriverInstall(ctx context.Context) (string, error) {
kr, err := driverkernel.FetchInfo(o.DriverKernelRelease, o.DriverKernelVersion)
if err != nil {
return "", err
}
o.Printer.Logger.Info("Running falcoctl driver install", o.Printer.Logger.Args( o.Printer.Logger.Info("Running falcoctl driver install", o.Printer.Logger.Args(
"driver version", o.Driver.Version, "driver version", o.Driver.Version,
"driver type", o.Driver.Type, "driver type", o.Driver.Type,
"driver name", o.Driver.Name, "driver name", o.Driver.Name,
"compile", o.Compile, "compile", o.Compile,
"download", o.Download, "download", o.Download,
"target", o.Distro.String(), "arch", kr.Architecture.ToNonDeb(),
"arch", o.Kr.Architecture.ToNonDeb(), "kernel release", kr.String(),
"kernel release", o.Kr.String(), "kernel version", kr.KernelVersion))
"kernel version", o.Kr.KernelVersion))
if !o.Driver.Type.HasArtifacts() { if !o.Driver.Type.HasArtifacts() {
o.Printer.Logger.Info("No artifacts needed for the selected driver.") o.Printer.Logger.Info("No artifacts needed for the selected driver.")
@ -120,8 +131,9 @@ func (o *driverInstallOptions) RunDriverInstall(ctx context.Context) (string, er
return "", nil return "", nil
} }
if o.Distro.String() == driverdistro.UndeterminedDistro { d, err := driverdistro.Discover(kr, o.Driver.HostRoot)
if o.Compile { if err != nil {
if errors.Is(err, driverdistro.ErrUnsupported) && o.Compile {
o.Download = false o.Download = false
o.Printer.Logger.Info( o.Printer.Logger.Info(
"Detected an unsupported target system, please get in touch with the Falco community. Trying to compile anyway.") "Detected an unsupported target system, please get in touch with the Falco community. Trying to compile anyway.")
@ -129,6 +141,7 @@ func (o *driverInstallOptions) RunDriverInstall(ctx context.Context) (string, er
return "", fmt.Errorf("detected an unsupported target system, please get in touch with the Falco community") return "", fmt.Errorf("detected an unsupported target system, please get in touch with the Falco community")
} }
} }
o.Printer.Logger.Info("Found distro", o.Printer.Logger.Args("target", d))
var ( var (
dest string dest string
@ -138,14 +151,14 @@ func (o *driverInstallOptions) RunDriverInstall(ctx context.Context) (string, er
if !o.Printer.DisableStyling { if !o.Printer.DisableStyling {
o.Printer.Spinner, _ = o.Printer.Spinner.Start("Cleaning up existing drivers") o.Printer.Spinner, _ = o.Printer.Spinner.Start("Cleaning up existing drivers")
} }
err := o.Driver.Type.Cleanup(o.Printer.WithWriter(&buf), o.Driver.Name) err = o.Driver.Type.Cleanup(o.Printer.WithWriter(&buf), o.Driver.Name)
if o.Printer.Spinner != nil { if o.Printer.Spinner != nil {
_ = o.Printer.Spinner.Stop() _ = o.Printer.Spinner.Stop()
} }
if o.Printer.Logger.Formatter == pterm.LogFormatterJSON { if o.Printer.Logger.Formatter == pterm.LogFormatterJSON {
// Only print formatted text if we are formatting to json // Only print formatted text if we are formatting to json
out := strings.ReplaceAll(buf.String(), "\n", ";") out := strings.ReplaceAll(buf.String(), "\n", ";")
o.Printer.Logger.Info("Driver cleanup", o.Printer.Logger.Args("output", out)) o.Printer.Logger.Info("Driver build", o.Printer.Logger.Args("output", out))
} else { } else {
// Print much more readable output as-is // Print much more readable output as-is
o.Printer.DefaultText.Print(buf.String()) o.Printer.DefaultText.Print(buf.String())
@ -160,15 +173,14 @@ func (o *driverInstallOptions) RunDriverInstall(ctx context.Context) (string, er
if !o.Printer.DisableStyling { if !o.Printer.DisableStyling {
o.Printer.Spinner, _ = o.Printer.Spinner.Start("Trying to download the driver") o.Printer.Spinner, _ = o.Printer.Spinner.Start("Trying to download the driver")
} }
dest, err = driverdistro.Download(ctx, o.Distro, o.Printer.WithWriter(&buf), o.Kr, o.Driver.Name, dest, err = driverdistro.Download(ctx, d, o.Printer.WithWriter(&buf), kr, o.Driver.Name, o.Driver.Type, o.Driver.Version, o.Driver.Repos)
o.Driver.Type, o.Driver.Version, o.Driver.Repos, o.HTTPHeaders)
if o.Printer.Spinner != nil { if o.Printer.Spinner != nil {
_ = o.Printer.Spinner.Stop() _ = o.Printer.Spinner.Stop()
} }
if o.Printer.Logger.Formatter == pterm.LogFormatterJSON { if o.Printer.Logger.Formatter == pterm.LogFormatterJSON {
// Only print formatted text if we are formatting to json // Only print formatted text if we are formatting to json
out := strings.ReplaceAll(buf.String(), "\n", ";") out := strings.ReplaceAll(buf.String(), "\n", ";")
o.Printer.Logger.Info("Driver download", o.Printer.Logger.Args("output", out)) o.Printer.Logger.Info("Driver build", o.Printer.Logger.Args("output", out))
} else { } else {
// Print much more readable output as-is // Print much more readable output as-is
o.Printer.DefaultText.Print(buf.String()) o.Printer.DefaultText.Print(buf.String())
@ -193,7 +205,7 @@ func (o *driverInstallOptions) RunDriverInstall(ctx context.Context) (string, er
if !o.Printer.DisableStyling { if !o.Printer.DisableStyling {
o.Printer.Spinner, _ = o.Printer.Spinner.Start("Trying to build the driver") o.Printer.Spinner, _ = o.Printer.Spinner.Start("Trying to build the driver")
} }
dest, err = driverdistro.Build(ctx, o.Distro, o.Printer.WithWriter(&buf), o.Kr, o.Driver.Name, o.Driver.Type, o.Driver.Version, o.DownloadHeaders) dest, err = driverdistro.Build(ctx, d, o.Printer.WithWriter(&buf), kr, o.Driver.Name, o.Driver.Type, o.Driver.Version)
if o.Printer.Spinner != nil { if o.Printer.Spinner != nil {
_ = o.Printer.Spinner.Stop() _ = o.Printer.Spinner.Stop()
} }
@ -207,6 +219,7 @@ func (o *driverInstallOptions) RunDriverInstall(ctx context.Context) (string, er
} }
buf.Reset() buf.Reset()
if err == nil { if err == nil {
o.Printer.Logger.Info("Driver built.", o.Printer.Logger.Args("path", dest))
return dest, nil return dest, nil
} }
if errors.Is(err, driverdistro.ErrAlreadyPresent) { if errors.Is(err, driverdistro.ErrAlreadyPresent) {

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -26,7 +26,8 @@ import (
) )
//nolint:lll // no need to check for line length. //nolint:lll // no need to check for line length.
var driverInstallHelp = `Install previously configured driver, either downloading it or attempting a build. var driverInstallHelp = `[Preview] Install previously configured driver, either downloading it or attempting a build.
** This command is in preview and under development. **
Usage: Usage:
falcoctl driver install [flags] falcoctl driver install [flags]
@ -34,22 +35,20 @@ Usage:
Flags: Flags:
--compile Whether to enable local compilation of drivers (default true) --compile Whether to enable local compilation of drivers (default true)
--download Whether to enable download of prebuilt drivers (default true) --download Whether to enable download of prebuilt drivers (default true)
--download-headers Whether to enable automatic kernel headers download where supported (default true)
-h, --help help for install -h, --help help for install
--http-headers string Optional comma-separated list of headers for the http GET request (e.g. --http-headers='x-emc-namespace: default,Proxy-Authenticate: Basic'). Not necessary if default repo is used
--http-insecure Whether you want to allow insecure downloads or not --http-insecure Whether you want to allow insecure downloads or not
--http-timeout duration Timeout for each http try (default 1m0s) --http-timeout duration Timeout for each http try (default 1m0s)
--kernelrelease string Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')
--kernelversion string Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')
Global Flags: Global Flags:
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml") --config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
--host-root string Driver host root to be used. (default "/") --host-root string Driver host root to be used. (default "/")
--kernelrelease string Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')
--kernelversion string Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')
--log-format string Set formatting for logs (color, text, json) (default "color") --log-format string Set formatting for logs (color, text, json) (default "color")
--log-level string Set level for logs (info, warn, debug, trace) (default "info") --log-level string Set level for logs (info, warn, debug, trace) (default "info")
--name string Driver name to be used. (default "falco") --name string Driver name to be used. (default "falco")
--repo strings Driver repo to be used. (default [https://download.falco.org/driver]) --repo strings Driver repo to be used. (default [https://download.falco.org/driver])
--type strings Driver types allowed in descending priority order (ebpf, kmod, modern_ebpf) (default [modern_ebpf,kmod,ebpf]) --type string Driver type to be used (auto, ebpf, kmod, modern_ebpf) (default "kmod")
--version string Driver version to be used. --version string Driver version to be used.
` `
@ -115,7 +114,8 @@ var _ = Describe("install", func() {
BeforeEach(func() { BeforeEach(func() {
args = []string{driverCmd, installCmd, "--config", configFile, "--type", "foo", "--version", "1.0.0+driver"} args = []string{driverCmd, installCmd, "--config", configFile, "--type", "foo", "--version", "1.0.0+driver"}
}) })
addAssertFailedBehavior(`ERROR unsupported driver type specified: foo`) addAssertFailedBehavior(`ERROR invalid argument "foo" for "--type" flag: invalid argument "foo",` +
` please provide one of (auto, ebpf, kmod, modern_ebpf)`)
}) })
}) })

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -16,11 +16,14 @@
package driverprintenv package driverprintenv
import ( import (
"errors"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/context" "golang.org/x/net/context"
driverdistro "github.com/falcosecurity/falcoctl/pkg/driver/distro"
driverkernel "github.com/falcosecurity/falcoctl/pkg/driver/kernel"
"github.com/falcosecurity/falcoctl/pkg/options" "github.com/falcosecurity/falcoctl/pkg/options"
) )
@ -39,9 +42,10 @@ func NewDriverPrintenvCmd(ctx context.Context, opt *options.Common, driver *opti
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "printenv [flags]", Use: "printenv [flags]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: "Print env vars", Short: "[Preview] Print env vars",
Long: `Print variables used by driver as env vars.`, Long: `[Preview] Print variables used by driver as env vars.
RunE: func(_ *cobra.Command, _ []string) error { ** This command is in preview and under development. **`,
RunE: func(cmd *cobra.Command, args []string) error {
return o.RunDriverPrintenv(ctx) return o.RunDriverPrintenv(ctx)
}, },
} }
@ -54,12 +58,27 @@ func (o *driverPrintenvOptions) RunDriverPrintenv(_ context.Context) error {
o.Printer.DefaultText.Printf("DRIVER_VERSION=%q\n", o.Driver.Version) o.Printer.DefaultText.Printf("DRIVER_VERSION=%q\n", o.Driver.Version)
o.Printer.DefaultText.Printf("DRIVER_NAME=%q\n", o.Driver.Name) o.Printer.DefaultText.Printf("DRIVER_NAME=%q\n", o.Driver.Name)
o.Printer.DefaultText.Printf("HOST_ROOT=%q\n", o.Driver.HostRoot) o.Printer.DefaultText.Printf("HOST_ROOT=%q\n", o.Driver.HostRoot)
o.Printer.DefaultText.Printf("TARGET_ID=%q\n", o.Distro.String())
o.Printer.DefaultText.Printf("ARCH=%q\n", o.Kr.Architecture.ToNonDeb()) kr, err := driverkernel.FetchInfo("", "")
o.Printer.DefaultText.Printf("KERNEL_RELEASE=%q\n", o.Kr.String()) if err != nil {
o.Printer.DefaultText.Printf("KERNEL_VERSION=%q\n", o.Kr.KernelVersion) return err
fixedKr := o.Distro.FixupKernel(o.Kr) }
d, err := driverdistro.Discover(kr, o.Driver.HostRoot)
if err != nil {
if !errors.Is(err, driverdistro.ErrUnsupported) {
return err
}
}
o.Printer.DefaultText.Printf("TARGET_ID=%q\n", d.String())
o.Printer.DefaultText.Printf("ARCH=%q\n", kr.Architecture.ToNonDeb())
o.Printer.DefaultText.Printf("KERNEL_RELEASE=%q\n", kr.String())
o.Printer.DefaultText.Printf("KERNEL_VERSION=%q\n", kr.KernelVersion)
fixedKr := d.FixupKernel(kr)
o.Printer.DefaultText.Printf("FIXED_KERNEL_RELEASE=%q\n", fixedKr.String()) o.Printer.DefaultText.Printf("FIXED_KERNEL_RELEASE=%q\n", fixedKr.String())
o.Printer.DefaultText.Printf("FIXED_KERNEL_VERSION=%q\n", fixedKr.KernelVersion) o.Printer.DefaultText.Printf("FIXED_KERNEL_VERSION=%q\n", fixedKr.KernelVersion)
return nil return nil
} }

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -28,8 +28,8 @@ import (
"github.com/falcosecurity/falcoctl/cmd" "github.com/falcosecurity/falcoctl/cmd"
) )
//nolint:lll // no need to check for line length. var driverPrintenvHelp = `[Preview] Print variables used by driver as env vars.
var driverPrintenvHelp = `Print variables used by driver as env vars. ** This command is in preview and under development. **
Usage: Usage:
falcoctl driver printenv [flags] falcoctl driver printenv [flags]
@ -40,17 +40,15 @@ Flags:
Global Flags: Global Flags:
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml") --config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
--host-root string Driver host root to be used. (default "/") --host-root string Driver host root to be used. (default "/")
--kernelrelease string Specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')
--kernelversion string Specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')
--log-format string Set formatting for logs (color, text, json) (default "color") --log-format string Set formatting for logs (color, text, json) (default "color")
--log-level string Set level for logs (info, warn, debug, trace) (default "info") --log-level string Set level for logs (info, warn, debug, trace) (default "info")
--name string Driver name to be used. (default "falco") --name string Driver name to be used. (default "falco")
--repo strings Driver repo to be used. (default [https://download.falco.org/driver]) --repo strings Driver repo to be used. (default [https://download.falco.org/driver])
--type strings Driver types allowed in descending priority order (ebpf, kmod, modern_ebpf) (default [modern_ebpf,kmod,ebpf]) --type string Driver type to be used (auto, ebpf, kmod, modern_ebpf) (default "kmod")
--version string Driver version to be used. --version string Driver version to be used.
` `
var driverPrintenvDefaultConfig = `DRIVER=".*" var driverPrintenvDefaultConfig = `DRIVER="kmod"
DRIVERS_REPO="https:\/\/download\.falco\.org\/driver" DRIVERS_REPO="https:\/\/download\.falco\.org\/driver"
DRIVER_VERSION="1.0.0\+driver" DRIVER_VERSION="1.0.0\+driver"
DRIVER_NAME="falco" DRIVER_NAME="falco"
@ -118,7 +116,8 @@ var _ = Describe("printenv", func() {
BeforeEach(func() { BeforeEach(func() {
args = []string{driverCmd, printenvCmd, "--config", configFile, "--type", "foo", "--version", "1.0.0+driver"} args = []string{driverCmd, printenvCmd, "--config", configFile, "--type", "foo", "--version", "1.0.0+driver"}
}) })
addAssertFailedBehavior(`unsupported driver type specified: foo`) addAssertFailedBehavior(`ERROR invalid argument "foo" for "--type" flag: invalid argument "foo",` +
` please provide one of (auto, ebpf, kmod, modern_ebpf)`)
}) })
}) })

View File

@ -16,31 +16,21 @@
package basic package basic
import ( import (
"bufio"
"context" "context"
"fmt" "fmt"
"io"
"os"
"strings"
credentials "github.com/oras-project/oras-credentials-go"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"
"oras.land/oras-go/v2/registry/remote/credentials"
"github.com/falcosecurity/falcoctl/internal/config" "github.com/falcosecurity/falcoctl/internal/config"
"github.com/falcosecurity/falcoctl/internal/login/basic" "github.com/falcosecurity/falcoctl/internal/login/basic"
"github.com/falcosecurity/falcoctl/internal/utils" "github.com/falcosecurity/falcoctl/internal/utils"
"github.com/falcosecurity/falcoctl/pkg/oci/authn" "github.com/falcosecurity/falcoctl/pkg/oci/authn"
"github.com/falcosecurity/falcoctl/pkg/options" "github.com/falcosecurity/falcoctl/pkg/options"
"github.com/falcosecurity/falcoctl/pkg/output"
) )
type loginOptions struct { type loginOptions struct {
*options.Common *options.Common
username string
password string
passwordFromStdin bool
} }
// NewBasicCmd returns the basic command. // NewBasicCmd returns the basic command.
@ -53,56 +43,22 @@ func NewBasicCmd(ctx context.Context, opt *options.Common) *cobra.Command {
Use: "basic [hostname]", Use: "basic [hostname]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: "Login to an OCI registry", Short: "Login to an OCI registry",
Long: `Login to an OCI registry Long: "Login to an OCI registry to push and pull artifacts",
Example - Log in with username and password from command line flags:
falcoctl registry auth basic -u username -p password localhost:5000
Example - Login with username and password from env variables:
FALCOCTL_REGISTRY_AUTH_BASIC_USERNAME=username FALCOCTL_REGISTRY_AUTH_BASIC_PASSWORD=password falcoctl registry auth basic localhost:5000
Example - Login with username and password from stdin:
falcoctl registry auth basic -u username --password-stdin localhost:5000
Example - Login with username and password in an interactive prompt:
falcoctl registry auth basic localhost:5000
`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
PreRunE: func(cmd *cobra.Command, args []string) error {
_ = viper.BindPFlag("registry.auth.basic.username", cmd.Flags().Lookup("username"))
_ = viper.BindPFlag("registry.auth.basic.password", cmd.Flags().Lookup("password"))
_ = viper.BindPFlag("registry.auth.basic.password_stdin", cmd.Flags().Lookup("password-stdin"))
o.username = viper.GetString("registry.auth.basic.username")
o.password = viper.GetString("registry.auth.basic.password")
o.passwordFromStdin = viper.GetBool("registry.auth.basic.password_stdin")
return nil
},
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return o.RunBasic(ctx, args) return o.RunBasic(ctx, args)
}, },
} }
cmd.Flags().StringVarP(&o.username, "username", "u", "", "registry username")
cmd.Flags().StringVarP(&o.password, "password", "p", "", "registry password")
cmd.Flags().BoolVar(&o.passwordFromStdin, "password-stdin", false, "read password from stdin")
return cmd return cmd
} }
// RunBasic executes the business logic for the basic command. // RunBasic executes the business logic for the basic command.
func (o *loginOptions) RunBasic(ctx context.Context, args []string) error { func (o *loginOptions) RunBasic(ctx context.Context, args []string) error {
var reg string reg := args[0]
logger := o.Printer.Logger logger := o.Printer.Logger
user, token, err := utils.GetCredentials(o.Printer)
// Allow to have the registry expressed as a ref, but actually extract it.
reg, err := utils.GetRegistryFromRef(args[0])
if err != nil { if err != nil {
reg = args[0]
}
if err := getCredentials(o.Printer, o); err != nil {
return err return err
} }
@ -117,46 +73,11 @@ func (o *loginOptions) RunBasic(ctx context.Context, args []string) error {
return fmt.Errorf("unable to create new store: %w", err) return fmt.Errorf("unable to create new store: %w", err)
} }
if err := basic.Login(ctx, client, credentialStore, reg, o.username, o.password); err != nil { if err := basic.Login(ctx, client, credentialStore, reg, user, token); err != nil {
return err return err
} }
logger.Debug("Credentials added", logger.Args("credential store", config.RegistryCredentialConfPath())) logger.Debug("Credentials added", logger.Args("credential store", config.RegistryCredentialConfPath()))
logger.Info("Login succeeded", logger.Args("registry", reg, "user", o.username)) logger.Info("Login succeeded", logger.Args("registry", reg, "user", user))
return nil
}
// getCredentials is used to retrieve username and password from standard input.
func getCredentials(p *output.Printer, opt *loginOptions) error {
reader := bufio.NewReader(os.Stdin)
if opt.username == "" {
p.DefaultText.Print(p.FormatTitleAsLoggerInfo("Enter username:"))
username, err := reader.ReadString('\n')
if err != nil {
return err
}
opt.username = strings.TrimSpace(username)
}
if opt.password == "" {
if opt.passwordFromStdin {
password, err := io.ReadAll(os.Stdin)
if err != nil {
return err
}
opt.password = strings.TrimSuffix(string(password), "\n")
opt.password = strings.TrimSuffix(opt.password, "\r")
} else {
p.DefaultText.Print(p.FormatTitleAsLoggerInfo("Enter password: "))
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return err
}
opt.password = string(bytePassword)
}
}
return nil return nil
} }

View File

@ -59,34 +59,7 @@ Global Flags:
` `
//nolint:unused // false positive //nolint:unused // false positive
var registryAuthBasicHelp = `Login to an OCI registry var registryAuthBasicHelp = `Login to an OCI registry to push and pull artifacts`
Example - Log in with username and password from command line flags:
falcoctl registry auth basic -u username -p password localhost:5000
Example - Login with username and password from env variables:
FALCOCTL_REGISTRY_AUTH_BASIC_USERNAME=username FALCOCTL_REGISTRY_AUTH_BASIC_PASSWORD=password falcoctl registry auth basic localhost:5000
Example - Login with username and password from stdin:
falcoctl registry auth basic -u username --password-stdin localhost:5000
Example - Login with username and password in an interactive prompt:
falcoctl registry auth basic localhost:5000
Usage:
falcoctl registry auth basic [hostname]
Flags:
-h, --help help for basic
-p, --password string registry password
--password-stdin read password from stdin
-u, --username string registry username
Global Flags:
--config string config file to be used for falcoctl (default "/etc/falcoctl/falcoctl.yaml")
--log-format string Set formatting for logs (color, text, json) (default "color")
--log-level string Set level for logs (info, warn, debug, trace) (default "info")
`
//nolint:unused // false positive //nolint:unused // false positive
var registryAuthBasicAssertFailedBehavior = func(usage, specificError string) { var registryAuthBasicAssertFailedBehavior = func(usage, specificError string) {

View File

@ -297,7 +297,7 @@ var registryPullTests = Describe("pull", func() {
args = []string{registryCmd, pullCmd, newReg, "--plain-http", "--config", configFile} args = []string{registryCmd, pullCmd, newReg, "--plain-http", "--config", configFile}
}) })
pullAssertFailedBehavior(registryPullUsage, fmt.Sprintf("ERROR unable to create new repository with ref %s: "+ pullAssertFailedBehavior(registryPullUsage, fmt.Sprintf("ERROR unable to create new repository with ref %s: "+
"invalid reference: invalid digest %q: invalid checksum digest format\n", newReg, "something")) "invalid reference: invalid digest; invalid checksum digest format\n", newReg))
}) })
When("invalid platform", func() { When("invalid platform", func() {

View File

@ -22,10 +22,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/blang/semver/v4"
"github.com/pterm/pterm"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"github.com/falcosecurity/falcoctl/internal/utils" "github.com/falcosecurity/falcoctl/internal/utils"
"github.com/falcosecurity/falcoctl/pkg/oci" "github.com/falcosecurity/falcoctl/pkg/oci"
@ -52,10 +49,6 @@ Example - Push artifact "myplugin.tar.gz" of type "plugin" for multiple platform
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile": Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile":
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" with floating tags for the major and minor versions (0 and 0.1):
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz \
--add-floating-tags
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" to an insecure registry: Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" to an insecure registry:
falcoctl registry push --type rulesfile --version "0.1.2" --plain-http localhost:5000/myrulesfile:latest myrulesfile.tar.gz falcoctl registry push --type rulesfile --version "0.1.2" --plain-http localhost:5000/myrulesfile:latest myrulesfile.tar.gz
@ -80,7 +73,7 @@ type pushOptions struct {
*options.Registry *options.Registry
} }
func (o *pushOptions) validate() error { func (o pushOptions) validate() error {
return o.Artifact.Validate() return o.Artifact.Validate()
} }
@ -127,8 +120,8 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error {
ref := args[0] ref := args[0]
paths := args[1:] paths := args[1:]
// When creating the tar.gz archives we need to remove them after we are done. // When creating the tar.gz archives we need to remove them after we are done.
// Holds the path for each temporary dir. // We save the temporary dir where they live here.
var toBeDeletedTmpDirs []string var toBeDeleted string
logger := o.Printer.Logger logger := o.Printer.Logger
registry, err := utils.GetRegistryFromRef(ref) registry, err := utils.GetRegistryFromRef(ref)
@ -148,41 +141,35 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error {
logger.Info("Preparing to push artifact", o.Printer.Logger.Args("name", args[0], "type", o.ArtifactType)) logger.Info("Preparing to push artifact", o.Printer.Logger.Args("name", args[0], "type", o.ArtifactType))
// Make sure to remove temporary working dirs. // Make sure to remove temporary working dir.
defer func() { defer func() {
for _, dir := range toBeDeletedTmpDirs { if err := os.RemoveAll(toBeDeleted); err != nil {
logger.Debug("Removing temporary dir", logger.Args("name", dir)) logger.Warn("Unable to remove temporary dir", logger.Args("name", toBeDeleted, "error", err.Error()))
if err := os.RemoveAll(dir); err != nil {
logger.Warn("Unable to remove temporary dir", logger.Args("name", dir, "error", err.Error()))
}
} }
}() }()
config := &oci.ArtifactConfig{
Name: o.Name,
Version: o.Version,
}
for i, p := range paths { for i, p := range paths {
if err = utils.IsTarGz(filepath.Clean(p)); err != nil && !errors.Is(err, utils.ErrNotTarGz) { if err = utils.IsTarGz(filepath.Clean(p)); err != nil && !errors.Is(err, utils.ErrNotTarGz) {
return err return err
} else if err == nil { } else if err == nil {
continue continue
} else { } else {
if o.ArtifactType == oci.Rulesfile { path, err := utils.CreateTarGzArchive(p)
if config, err = rulesConfigLayer(o.Printer.Logger, p, o.Artifact); err != nil {
return err
}
}
path, err := utils.CreateTarGzArchive("", p, true)
if err != nil { if err != nil {
return err return err
} }
paths[i] = path paths[i] = path
toBeDeletedTmpDirs = append(toBeDeletedTmpDirs, filepath.Dir(path)) if toBeDeleted == "" {
toBeDeleted = filepath.Dir(path)
}
} }
} }
// Setup OCI artifact configuration
config := oci.ArtifactConfig{
Name: o.Name,
Version: o.Version,
}
if config.Name == "" { if config.Name == "" {
// extract artifact name from ref, if not provided by the user // extract artifact name from ref, if not provided by the user
if config.Name, err = utils.NameFromRef(ref); err != nil { if config.Name, err = utils.NameFromRef(ref); err != nil {
@ -196,18 +183,10 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error {
return err return err
} }
if o.AutoFloatingTags {
v, err := semver.Parse(o.Version)
if err != nil {
return fmt.Errorf("expected semver for the flag \"--version\": %w", err)
}
o.Tags = append(o.Tags, o.Version, fmt.Sprintf("%v", v.Major), fmt.Sprintf("%v.%v", v.Major, v.Minor))
}
opts := ocipusher.Options{ opts := ocipusher.Options{
ocipusher.WithTags(o.Tags...), ocipusher.WithTags(o.Tags...),
ocipusher.WithAnnotationSource(o.AnnotationSource), ocipusher.WithAnnotationSource(o.AnnotationSource),
ocipusher.WithArtifactConfig(*config), ocipusher.WithArtifactConfig(config),
} }
switch o.ArtifactType { switch o.ArtifactType {
@ -224,120 +203,7 @@ func (o *pushOptions) runPush(ctx context.Context, args []string) error {
return err return err
} }
logger.Info("Artifact pushed", logger.Args("name", args[0], "type", res.Type, "digest", res.RootDigest)) logger.Info("Artifact pushed", logger.Args("name", args[0], "type", res.Type, "digest", res.Digest))
return nil return nil
} }
const (
// depsKey is the key for deps in the rulesfiles.
depsKey = "required_plugin_versions"
// engineKey is the key in the rulesfiles.
engineKey = "required_engine_version"
// engineRequirementKey is used as name for the engine requirement in the config layer for the rulesfile artifacts.
engineRequirementKey = "engine_version_semver"
)
func rulesConfigLayer(logger *pterm.Logger, filePath string, artifactOptions *options.Artifact) (*oci.ArtifactConfig, error) {
var data []map[string]interface{}
// Setup OCI artifact configuration
config := oci.ArtifactConfig{
Name: artifactOptions.Name,
Version: artifactOptions.Version,
}
yamlFile, err := os.ReadFile(filepath.Clean(filePath))
if err != nil {
return nil, fmt.Errorf("unable to open rulesfile %s: %w", filePath, err)
}
if err := yaml.Unmarshal(yamlFile, &data); err != nil {
return nil, fmt.Errorf("unable to unmarshal rulesfile %s: %w", filePath, err)
}
// Parse the artifact dependencies.
// Check if the user has provided any.
if len(artifactOptions.Dependencies) != 0 {
logger.Info("Dependencies provided by user", logger.Args("rulesfile", filePath))
if err = config.ParseDependencies(artifactOptions.Dependencies...); err != nil {
return nil, err
}
} else {
// If no user provided then try to parse them from the rulesfile.
var found bool
logger.Info("Parsing dependencies from: ", logger.Args("rulesfile", filePath))
var requiredPluginVersionsEntry interface{}
var ok bool
for _, entry := range data {
if requiredPluginVersionsEntry, ok = entry[depsKey]; !ok {
continue
}
var deps []oci.ArtifactDependency
byteData, err := yaml.Marshal(requiredPluginVersionsEntry)
if err != nil {
return nil, fmt.Errorf("unable to parse dependencies from rulesfile: %w", err)
}
err = yaml.Unmarshal(byteData, &deps)
if err != nil {
return nil, fmt.Errorf("unable to parse dependencies from rulesfile: %w", err)
}
logger.Info("Dependencies correctly parsed from rulesfile")
// Set the deps.
config.Dependencies = deps
found = true
break
}
if !found {
logger.Warn("No dependencies were provided by the user and none were found in the rulesfile.")
}
}
// Parse the requirements.
// Check if the user has provided any.
if len(artifactOptions.Requirements) != 0 {
logger.Info("Requirements provided by user")
if err = config.ParseRequirements(artifactOptions.Requirements...); err != nil {
return nil, err
}
} else {
var found bool
var engineVersion string
logger.Info("Parsing requirements from: ", logger.Args("rulesfile", filePath))
// If no user provided requirements then try to parse them from the rulesfile.
for _, entry := range data {
if requiredEngineVersionEntry, ok := entry[engineKey]; ok {
// Check if the version is an int. This is for backward compatibility. The engine version used to be an
// int but internally used by falco as a semver minor version.
// 15 -> 0.15.0
if engVersionInt, ok := requiredEngineVersionEntry.(int); ok {
engineVersion = fmt.Sprintf("0.%d.0", engVersionInt)
} else {
engineVersion, ok = requiredEngineVersionEntry.(string)
if !ok {
return nil, fmt.Errorf("%s must be an int or a string respecting the semver specification, got type %T", engineKey, requiredEngineVersionEntry)
}
// Check if it is in semver format.
if _, err := semver.Parse(engineVersion); err != nil {
return nil, fmt.Errorf("%s must be in semver format: %w", engineVersion, err)
}
}
// Set the requirements.
config.Requirements = []oci.ArtifactRequirement{{
Name: engineRequirementKey,
Version: engineVersion,
}}
found = true
break
}
}
if !found {
logger.Warn("No requirements were provided by the user and none were found in the rulesfile.")
}
}
return &config, nil
}

View File

@ -1,217 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package push_test
// revive:disable
import (
"fmt"
"os"
"path/filepath"
"regexp"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/falcosecurity/falcoctl/cmd"
"github.com/falcosecurity/falcoctl/internal/utils"
"github.com/falcosecurity/falcoctl/pkg/oci"
testutils "github.com/falcosecurity/falcoctl/pkg/test"
)
// revive:enable
var _ = Describe("pushing plugins", func() {
var (
registryCmd = "registry"
pushCmd = "push"
version = "1.1.1"
// fullRepoName is set each time before each test.
fullRepoName string
// repoName same as fullRepoName.
repoName string
// It is set in the config layer.
artifactNameInConfigLayer = "test-push-plugins"
pushedTags = []string{"tag1", "tag2", "latest"}
// Plugin's platforms.
platformARM64 = "linux/arm64"
platformAMD64 = "linux/amd64"
// Paths pointing to plugins that will be pushed.
// Some of the functions expect these two variable to be set to valid paths.
// They are set in beforeEach blocks by tests that need them.
pluginOne string
pluginTwo string
// Data fetched from registry and used for assertions.
pluginData *testutils.PluginArtifact
)
const (
// Used as flags for all the test cases.
requirement = "plugin_api_version:3.2.1"
anSource = "myrepo.com/rules.git"
pluginsRepoBaseName = "push-plugins-tests"
)
var AssertSuccessBehaviour = func(deps []oci.ArtifactDependency, reqs []oci.ArtifactRequirement, annotations map[string]string, platforms []string) {
It("should succeed", func() {
// We do not check the error here since we are checking it after
// pushing the artifact.
By("checking no error in output")
Expect(output).ShouldNot(gbytes.Say("ERROR"))
Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir"))
By("checking descriptor")
Expect(pluginData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageIndex))
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(pluginData.Descriptor.Digest.String())))
By("checking index")
Expect(pluginData.Index.Manifests).Should(HaveLen(len(platforms)))
By("checking platforms")
for _, p := range platforms {
Expect(pluginData.Platforms).Should(HaveKey(p))
}
By("checking config layers")
for plat, p := range pluginData.Platforms {
By(fmt.Sprintf("platform %s", plat))
Expect(p.Config.Version).Should(Equal(version))
Expect(p.Config.Name).Should(Equal(artifactNameInConfigLayer))
By("checking dependencies")
Expect(p.Config.Dependencies).Should(HaveLen(len(deps)))
for _, dep := range deps {
Expect(p.Config.Dependencies).Should(ContainElement(dep))
}
By("checking requirements")
Expect(p.Config.Requirements).Should(HaveLen(len(reqs)))
for _, req := range reqs {
Expect(p.Config.Requirements).Should(ContainElement(req))
}
By("checking annotations")
// The creation timestamp is always present.
Expect(p.Manifest.Annotations).Should(HaveLen(len(annotations) + 1))
for key, val := range annotations {
Expect(p.Manifest.Annotations).Should(HaveKeyWithValue(key, val))
}
}
By("checking tags")
Expect(pluginData.Tags).Should(HaveLen(len(pushedTags)))
Expect(pluginData.Tags).Should(ContainElements(pushedTags))
By("checking that temporary dirs have been removed")
Eventually(func() bool {
entries, err := os.ReadDir("/tmp")
Expect(err).ShouldNot(HaveOccurred())
for _, e := range entries {
if e.IsDir() {
matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name()))
Expect(err).ShouldNot(HaveOccurred())
if matched {
return true
}
}
}
return false
}).WithTimeout(5 * time.Second).Should(BeFalse())
})
}
// Each test gets its own root command and runs it.
// The err variable is asserted by each test.
JustBeforeEach(func() {
rootCmd = cmd.New(ctx, opt)
err = executeRoot(args)
})
JustAfterEach(func() {
// Reset the status after each test.
// This variable could be changed by single tests.
// Make sure to set them at their default values.
Expect(output.Clear()).ShouldNot(HaveOccurred())
artifactNameInConfigLayer = "test-plugin"
pushedTags = []string{"tag1", "tag2", "latest"}
pluginOne = ""
pluginTwo = ""
})
Context("success", func() {
JustBeforeEach(func() {
// Check the returned error before proceeding.
Expect(err).ShouldNot(HaveOccurred())
pluginData, err = testutils.FetchPluginFromRegistry(ctx, repoName, pushedTags[0], orasRegistry)
Expect(err).ShouldNot(HaveOccurred())
})
When("two platforms, with reqs and deps", func() {
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName)
pluginOne = rulesfileyaml
pluginTwo = plugintgz
args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, pluginTwo, "--type", "plugin", "--platform",
platformAMD64, "--platform", platformARM64, "--version", version, "--config", configFile,
"--plain-http", "--depends-on", "my-test:4.3.2", "--requires", requirement, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccessBehaviour([]oci.ArtifactDependency{{
Name: "my-test",
Version: "4.3.2",
Alternatives: nil,
}}, []oci.ArtifactRequirement{
{
Name: "plugin_api_version",
Version: "3.2.1",
},
}, map[string]string{
"org.opencontainers.image.source": anSource,
}, []string{
platformAMD64, platformARM64,
})
})
When("one platform, no reqs", func() {
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName)
pluginOne = plugintgz
args = []string{registryCmd, pushCmd, fullRepoName, pluginOne, "--type", "plugin", "--platform",
platformAMD64, "--version", version, "--config", configFile,
"--plain-http", "--depends-on", "my-test:4.3.2", "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
// We expect to succeed and that the requirement is empty.
AssertSuccessBehaviour([]oci.ArtifactDependency{{
Name: "my-test",
Version: "4.3.2",
Alternatives: nil,
}}, []oci.ArtifactRequirement{}, map[string]string{
"org.opencontainers.image.source": anSource,
}, []string{
platformAMD64,
})
})
})
})

View File

@ -1,655 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package push_test
// revive:disable
import (
"fmt"
"os"
"path/filepath"
"regexp"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/falcosecurity/falcoctl/cmd"
"github.com/falcosecurity/falcoctl/internal/utils"
"github.com/falcosecurity/falcoctl/pkg/oci"
testutils "github.com/falcosecurity/falcoctl/pkg/test"
)
// revive:enable
var _ = Describe("pushing rulesfiles", func() {
var (
registryCmd = "registry"
pushCmd = "push"
version = "1.1.1"
// registry/rulesRepoBaseName-randomInt
fullRepoName string
// rulesRepoBaseName-randomInt
repoName string
// It is set in the config layer.
artifactNameInConfigLayer = "test-rulesfile"
pushedTags = []string{"tag1", "tag2", "latest"}
// Variables passed as arguments to the push command. Each test case updates them
// to point to the file on disk living in pkg/test/data.
rulesfile string
// Data fetched from registry and used for assertions.
rulesfileData *testutils.RulesfileArtifact
)
const (
// Used as flags for all the test cases.
dep1 = "myplugin:1.2.3"
dep2 = "myplugin1:1.2.3|otherplugin:3.2.1"
req = "engine_version_semver:0.37.0"
anSource = "myrepo.com/rules.git"
rulesRepoBaseName = "push-rulesfile"
)
// We keep it inside the success context since need the variables of this context.
var AssertSuccesBehaviour = func(deps []oci.ArtifactDependency, reqs []oci.ArtifactRequirement, annotations map[string]string) {
It("should succeed", func() {
// We do not check the error here since we are checking it after
// pushing the artifact.
By("checking no error in output")
Expect(output).ShouldNot(gbytes.Say("ERROR"))
Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir"))
By("checking descriptor")
Expect(rulesfileData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageManifest))
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(rulesfileData.Descriptor.Digest.String())))
By("checking manifest")
Expect(rulesfileData.Layer.Manifest.Layers).Should(HaveLen(1))
By("checking platforms")
Expect(rulesfileData.Descriptor.Platform).Should(BeNil())
By("checking config layer")
Expect(rulesfileData.Layer.Config.Version).Should(Equal(version))
Expect(rulesfileData.Layer.Config.Name).Should(Equal(artifactNameInConfigLayer))
By("checking dependencies")
Expect(rulesfileData.Layer.Config.Dependencies).Should(HaveLen(len(deps)))
for _, dep := range deps {
Expect(rulesfileData.Layer.Config.Dependencies).Should(ContainElement(dep))
}
By("checking requirements")
Expect(rulesfileData.Layer.Config.Requirements).Should(HaveLen(len(reqs)))
for _, req := range reqs {
Expect(rulesfileData.Layer.Config.Requirements).Should(ContainElement(req))
}
By("checking annotations")
// The creation timestamp is always present.
Expect(rulesfileData.Layer.Manifest.Annotations).Should(HaveLen(len(annotations) + 1))
for key, val := range annotations {
Expect(rulesfileData.Layer.Manifest.Annotations).Should(HaveKeyWithValue(key, val))
}
By("checking tags")
Expect(rulesfileData.Tags).Should(HaveLen(len(pushedTags)))
Expect(rulesfileData.Tags).Should(ContainElements(pushedTags))
By("checking that temporary dirs have been removed")
Eventually(func() bool {
entries, err := os.ReadDir("/tmp")
Expect(err).ShouldNot(HaveOccurred())
for _, e := range entries {
if e.IsDir() {
matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name()))
Expect(err).ShouldNot(HaveOccurred())
if matched {
fmt.Println(e.Name())
return true
}
}
}
return false
}).WithTimeout(5 * time.Second).Should(BeFalse())
})
}
// Each test gets its own root command and runs it.
// The err variable is asserted by each test.
JustBeforeEach(func() {
rootCmd = cmd.New(ctx, opt)
err = executeRoot(args)
})
JustAfterEach(func() {
Expect(output.Clear()).ShouldNot(HaveOccurred())
// This variable could be changed by single tests.
// Make sure to set them at their default values.
artifactNameInConfigLayer = "test-rulesfile"
pushedTags = []string{"tag1", "tag2", "latest"}
rulesfile = ""
})
Context("success", func() {
// Here we are testing all the success cases for the push command. The artifact type used here is of type
// rulesfile. Keep in mind that here we are testing also the common flags that could be used by the plugin
// artifacts. So we are testing that common logic only once, and are doing it here.
JustBeforeEach(func() {
// This runs after the push command, so check the returned error before proceeding.
Expect(err).ShouldNot(HaveOccurred())
rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry)
Expect(err).ShouldNot(HaveOccurred())
})
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
})
When("with full flags and args", func() {
BeforeEach(func() {
rulesfile = rulesfileyaml
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "myplugin",
Version: "1.2.3",
Alternatives: nil,
}, {
Name: "myplugin1",
Version: "1.2.3",
Alternatives: []oci.Dependency{{
Name: "otherplugin",
Version: "3.2.1",
},
},
},
}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.37.0",
},
}, map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("no --name flag provided", func() {
BeforeEach(func() {
rulesfile = rulesfileyaml
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2]}
// Set name to the expected one.
artifactNameInConfigLayer = repoName
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "myplugin",
Version: "1.2.3",
Alternatives: nil,
}, {
Name: "myplugin1",
Version: "1.2.3",
Alternatives: []oci.Dependency{{
Name: "otherplugin",
Version: "3.2.1",
},
},
},
}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.37.0",
},
}, map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("no --annotation-source provided", func() {
BeforeEach(func() {
rulesfile = rulesfileyaml
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "myplugin",
Version: "1.2.3",
Alternatives: nil,
}, {
Name: "myplugin1",
Version: "1.2.3",
Alternatives: []oci.Dependency{{
Name: "otherplugin",
Version: "3.2.1",
},
},
},
}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.37.0",
},
}, map[string]string{})
})
When("no --tags provided", func() {
BeforeEach(func() {
rulesfile = rulesfileyaml
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
"--name", artifactNameInConfigLayer}
// We expect that latest tag is pushed, so set it in the pushed tags.
pushedTags = []string{"latest"}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "myplugin",
Version: "1.2.3",
Alternatives: nil,
}, {
Name: "myplugin1",
Version: "1.2.3",
Alternatives: []oci.Dependency{{
Name: "otherplugin",
Version: "3.2.1",
},
},
},
}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.37.0",
},
}, map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("no --depends-on flag provided", func() {
BeforeEach(func() {
rulesfile = rulesfileyaml
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{},
[]oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.37.0",
},
}, map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("no --requires flag provided", func() {
BeforeEach(func() {
rulesfile = rulesfileyaml
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "myplugin",
Version: "1.2.3",
Alternatives: nil,
}, {
Name: "myplugin1",
Version: "1.2.3",
Alternatives: []oci.Dependency{{
Name: "otherplugin",
Version: "3.2.1",
},
},
},
}, []oci.ArtifactRequirement{}, map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("only required flags", func() {
BeforeEach(func() {
rulesfile = rulesfileyaml
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http"}
// Set name to the expected one.
artifactNameInConfigLayer = repoName
// We expect that latest tag is pushed, so set it in the pushed tags.
pushedTags = []string{"latest"}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{},
[]oci.ArtifactRequirement{},
map[string]string{})
})
When("with add-floating-tags and the required flags", func() {
BeforeEach(func() {
rulesfile = rulesfileyaml
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--add-floating-tags", "--plain-http"}
// Set name to the expected one.
artifactNameInConfigLayer = repoName
// The semver tags are expected to be set.
pushedTags = []string{"1.1.1", "1.1", "1"}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{},
[]oci.ArtifactRequirement{},
map[string]string{})
})
When("with full flags and args but in tar.gz format", func() {
BeforeEach(func() {
rulesfile = rulesfiletgz
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "myplugin",
Version: "1.2.3",
Alternatives: nil,
}, {
Name: "myplugin1",
Version: "1.2.3",
Alternatives: []oci.Dependency{{
Name: "otherplugin",
Version: "3.2.1",
},
},
},
}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.37.0",
},
}, map[string]string{
"org.opencontainers.image.source": anSource,
})
})
Context("rulesfile deps and requirements", func() {
When("user provided deps", func() {
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
rulesfile = rulesFileWithDepsAndReq
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "myplugin",
Version: "1.2.3",
Alternatives: nil,
}, {
Name: "myplugin1",
Version: "1.2.3",
Alternatives: []oci.Dependency{{
Name: "otherplugin",
Version: "3.2.1",
},
},
},
}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.37.0",
},
}, map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("parsed from file deps", func() {
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
rulesfile = rulesFileWithDepsAndReq
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "cloudtrail",
Version: "0.2.3",
Alternatives: nil,
}, {
Name: "json",
Version: "0.2.2",
Alternatives: nil,
},
}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.10.0",
},
},
map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("parsed from file deps with alternatives", func() {
var data = `
- required_plugin_versions:
- name: k8saudit
version: 0.7.0
alternatives:
- name: k8saudit-eks
version: 0.4.0
- name: json
version: 0.7.0
`
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
tmpDir := GinkgoT().TempDir()
rulesfile, err = testutils.WriteToTmpFile(data, tmpDir)
Expect(err).ToNot(HaveOccurred())
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "json",
Version: "0.7.0",
Alternatives: nil,
}, {
Name: "k8saudit",
Version: "0.7.0",
Alternatives: []oci.Dependency{{
Name: "k8saudit-eks",
Version: "0.4.0",
},
},
},
}, []oci.ArtifactRequirement{},
map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("no deps at all", func() {
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
rulesfile = rulesfileyaml
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{}, []oci.ArtifactRequirement{},
map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("user provided requirement", func() {
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
rulesfile = rulesFileWithDepsAndReq
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "json",
Version: "0.2.2",
Alternatives: nil,
}, {
Name: "cloudtrail",
Version: "0.2.3",
Alternatives: nil,
},
}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.37.0",
},
},
map[string]string{
"org.opencontainers.image.source": anSource,
})
It("reqs should be the ones provided by the user", func() {
Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Requirements[0].Name,
rulesfileData.Layer.Config.Requirements[0].Version)).Should(Equal(req))
})
})
When("requirement parsed from file in semver format", func() {
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
rulesfile = rulesFileWithDepsAndReq
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{
{
Name: "json",
Version: "0.2.2",
Alternatives: nil,
}, {
Name: "cloudtrail",
Version: "0.2.3",
Alternatives: nil,
},
}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.10.0",
},
}, map[string]string{
"org.opencontainers.image.source": anSource,
})
})
When("requirement parsed from file in int format", func() {
var rulesfileContent = `
- required_engine_version: 10
`
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
tmpDir := GinkgoT().TempDir()
rulesfile, err = testutils.WriteToTmpFile(rulesfileContent, tmpDir)
Expect(err).ToNot(HaveOccurred())
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour([]oci.ArtifactDependency{}, []oci.ArtifactRequirement{
{
Name: "engine_version_semver",
Version: "0.10.0",
},
}, map[string]string{
"org.opencontainers.image.source": anSource,
})
})
})
})
Context("failure", func() {
When("requirement parsed from file -- invalid format (float)", func() {
var rulesFile = `
- required_engine_version: 10.0
`
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
tmpDir := GinkgoT().TempDir()
rulesfile, err = testutils.WriteToTmpFile(rulesFile, tmpDir)
Expect(err).ToNot(HaveOccurred())
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
It("should fail", func() {
Expect(err).Should(HaveOccurred())
Expect(output).Should(gbytes.Say(regexp.QuoteMeta("required_engine_version must be an int or a string respecting " +
"the semver specification, got type float64")))
})
})
When("requirement parsed from file -- invalid format (not semver)", func() {
var rulesFile = `
- required_engine_version: 10.0notsemver
`
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
tmpDir := GinkgoT().TempDir()
rulesfile, err = testutils.WriteToTmpFile(rulesFile, tmpDir)
Expect(err).ToNot(HaveOccurred())
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
// Set name to the expected one.
artifactNameInConfigLayer = repoName
// We expect that latest tag is pushed, so set it in the pushed tags.
pushedTags = []string{"latest"}
})
It("reqs should be the ones provided by the user", func() {
Expect(err).Should(HaveOccurred())
Expect(output).Should(gbytes.Say(regexp.QuoteMeta("10.0notsemver must be in semver format: No Major.Minor.Patch elements found")))
})
})
})
})

View File

@ -37,13 +37,14 @@ import (
testutils "github.com/falcosecurity/falcoctl/pkg/test" testutils "github.com/falcosecurity/falcoctl/pkg/test"
) )
//nolint:unused // false positive
const ( const (
rulesfiletgz = "../../../pkg/test/data/rules.tar.gz" rulesfiletgz = "../../../pkg/test/data/rules.tar.gz"
rulesfileyaml = "../../../pkg/test/data/rulesWithoutReqAndDeps.yaml" rulesfileyaml = "../../../pkg/test/data/rules.yaml"
rulesFileWithDepsAndReq = "../../../pkg/test/data/rules.yaml"
plugintgz = "../../../pkg/test/data/plugin.tar.gz" plugintgz = "../../../pkg/test/data/plugin.tar.gz"
) )
//nolint:unused // false positive
var ( var (
registry string registry string
ctx = context.Background() ctx = context.Background()
@ -101,6 +102,7 @@ var _ = AfterSuite(func() {
Expect(os.RemoveAll(configDir)).Should(Succeed()) Expect(os.RemoveAll(configDir)).Should(Succeed())
}) })
//nolint:unused // false positive
func executeRoot(args []string) error { func executeRoot(args []string) error {
rootCmd.SetArgs(args) rootCmd.SetArgs(args)
rootCmd.SetOut(output) rootCmd.SetOut(output)

View File

@ -18,20 +18,25 @@ package push_test
import ( import (
"fmt" "fmt"
"math/rand" "math/rand"
"os"
"path/filepath"
"regexp" "regexp"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes" "github.com/onsi/gomega/gbytes"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/falcosecurity/falcoctl/cmd" "github.com/falcosecurity/falcoctl/cmd"
"github.com/falcosecurity/falcoctl/internal/utils"
testutils "github.com/falcosecurity/falcoctl/pkg/test"
) )
//nolint:lll,unused // no need to check for line length.
var registryPushUsage = `Usage: var registryPushUsage = `Usage:
falcoctl registry push hostname/repo[:tag|@digest] file [flags] falcoctl registry push hostname/repo[:tag|@digest] file [flags]
Flags: Flags:
--add-floating-tags add the floating tags for the major and minor versions
--annotation-source string set annotation source for the artifact --annotation-source string set annotation source for the artifact
-d, --depends-on stringArray set an artifact dependency (can be specified multiple times). Example: "--depends-on my-plugin:1.2.3" -d, --depends-on stringArray set an artifact dependency (can be specified multiple times). Example: "--depends-on my-plugin:1.2.3"
-h, --help help for push -h, --help help for push
@ -66,10 +71,6 @@ Example - Push artifact "myplugin.tar.gz" of type "plugin" for multiple platform
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile": Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile":
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" with floating tags for the major and minor versions (0 and 0.1):
falcoctl registry push --type rulesfile --version "0.1.2" localhost:5000/myrulesfile:latest myrulesfile.tar.gz \
--add-floating-tags
Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" to an insecure registry: Example - Push artifact "myrulesfile.tar.gz" of type "rulesfile" to an insecure registry:
falcoctl registry push --type rulesfile --version "0.1.2" --plain-http localhost:5000/myrulesfile:latest myrulesfile.tar.gz falcoctl registry push --type rulesfile --version "0.1.2" --plain-http localhost:5000/myrulesfile:latest myrulesfile.tar.gz
@ -90,7 +91,6 @@ Usage:
falcoctl registry push hostname/repo[:tag|@digest] file [flags] falcoctl registry push hostname/repo[:tag|@digest] file [flags]
Flags: Flags:
--add-floating-tags add the floating tags for the major and minor versions
--annotation-source string set annotation source for the artifact --annotation-source string set annotation source for the artifact
-d, --depends-on stringArray set an artifact dependency (can be specified multiple times). Example: "--depends-on my-plugin:1.2.3" -d, --depends-on stringArray set an artifact dependency (can be specified multiple times). Example: "--depends-on my-plugin:1.2.3"
-h, --help help for push -h, --help help for push
@ -108,6 +108,7 @@ Global Flags:
--log-level string Set level for logs (info, warn, debug, trace) (default "info") --log-level string Set level for logs (info, warn, debug, trace) (default "info")
` `
//nolint:unused // false positive
var pushAssertFailedBehavior = func(usage, specificError string) { var pushAssertFailedBehavior = func(usage, specificError string) {
It("check that fails and the usage is not printed", func() { It("check that fails and the usage is not printed", func() {
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
@ -116,17 +117,27 @@ var pushAssertFailedBehavior = func(usage, specificError string) {
}) })
} }
//nolint:unused // false positive
var randomRulesRepoName = func(registry, repo string) (string, string) { var randomRulesRepoName = func(registry, repo string) (string, string) {
rName := fmt.Sprintf("%s-%d", repo, rand.Int()) rName := fmt.Sprintf("%s-%d", repo, rand.Int())
return rName, fmt.Sprintf("%s/%s", registry, rName) return rName, fmt.Sprintf("%s/%s", registry, rName)
} }
var _ = Describe("push", func() { //nolint:unused // false positive
var registryPushTests = Describe("push", func() {
var ( var (
registryCmd = "registry" registryCmd = "registry"
pushCmd = "push" pushCmd = "push"
) )
const (
// Used as flags for all the test cases.
dep1 = "myplugin:1.2.3"
dep2 = "myplugin1:1.2.3|otherplugin:3.2.1"
req = "engine_version:15"
anSource = "myrepo.com/rules.git"
)
// Each test gets its own root command and runs it. // Each test gets its own root command and runs it.
// The err variable is asserted by each test. // The err variable is asserted by each test.
JustBeforeEach(func() { JustBeforeEach(func() {
@ -196,22 +207,6 @@ var _ = Describe("push", func() {
"registry \"noregistry\": Get \"http://noregistry/v2/\": dial tcp: lookup noregistry") "registry \"noregistry\": Get \"http://noregistry/v2/\": dial tcp: lookup noregistry")
}) })
When("wrong semver for --version flag with --add-floating-tags", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, rulesRepo, rulesfiletgz, "--config", configFile, "--type", "rulesfile",
"--version", "notSemVer", "--add-floating-tags", "--plain-http"}
})
pushAssertFailedBehavior(registryPushUsage, "ERROR expected semver for the flag \"--version\": No Major.Minor.Patch elements found")
})
When("invalid character in semver for --version flag with --add-floating-tags", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, rulesRepo, rulesfiletgz, "--config", configFile, "--type", "rulesfile",
"--version", "1.1.a", "--add-floating-tags", "--plain-http"}
})
pushAssertFailedBehavior(registryPushUsage, "ERROR expected semver for the flag \"--version\": Invalid character(s) found in patch number \"a\"")
})
When("missing repository", func() { When("missing repository", func() {
BeforeEach(func() { BeforeEach(func() {
args = []string{registryCmd, pushCmd, registry, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1", "--plain-http"} args = []string{registryCmd, pushCmd, registry, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1", "--plain-http"}
@ -225,7 +220,7 @@ var _ = Describe("push", func() {
args = []string{registryCmd, pushCmd, newReg, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1", "--plain-http"} args = []string{registryCmd, pushCmd, newReg, rulesfiletgz, "--config", configFile, "--type", "rulesfile", "--version", "1.1.1", "--plain-http"}
}) })
pushAssertFailedBehavior(registryPushUsage, fmt.Sprintf("ERROR unable to create new repository with ref %s: "+ pushAssertFailedBehavior(registryPushUsage, fmt.Sprintf("ERROR unable to create new repository with ref %s: "+
"invalid reference: invalid digest %q: invalid checksum digest format\n", newReg, "something")) "invalid reference: invalid digest; invalid checksum digest format\n", newReg))
}) })
When("invalid requirement", func() { When("invalid requirement", func() {
@ -262,4 +257,303 @@ var _ = Describe("push", func() {
"flag: must be one of \"rulesfile\", \"plugin\", \"asset") "flag: must be one of \"rulesfile\", \"plugin\", \"asset")
}) })
}) })
Context("success", func() {
const (
rulesRepoBaseName = "push-rulesfile"
pluginsRepoBaseName = "push-plugins"
)
var (
version = "1.1.1"
// registry/rulesRepoBaseName-randomInt
fullRepoName string
// rulesRepoBaseName-randomInt
repoName string
// It is set in the config layer.
artifactNameInConfigLayer = "test-rulesfile"
pushedTags = []string{"tag1", "tag2", "latest"}
// Variables passed as arguments to the push command. Each test case updates them
// to point to the file on disk living in pkg/test/data.
rulesfile string
plugin string
pluginRaw string
// Plugin's platforms.
platformARM64 = "linux/arm64"
platformAMD64 = "linux/amd64"
// Data fetched from registry and used for assertions.
pluginData *testutils.PluginArtifact
rulesfileData *testutils.RulesfileArtifact
)
// We keep it inside the success context since need the variables of this context.
var AssertSuccesBehaviour = func(dependencies, requirements, annotation bool) {
It("should succeed", func() {
// We do not check the error here since we are checking it before
// pulling the artifact.
By("checking no error in output")
Expect(output).ShouldNot(gbytes.Say("ERROR"))
Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir"))
By("checking descriptor")
Expect(rulesfileData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageManifest))
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(rulesfileData.Descriptor.Digest.String())))
By("checking manifest")
Expect(rulesfileData.Layer.Manifest.Layers).Should(HaveLen(1))
if annotation {
Expect(rulesfileData.Layer.Manifest.Annotations).Should(HaveKeyWithValue("org.opencontainers.image.source", anSource))
} else {
Expect(rulesfileData.Layer.Manifest.Annotations).ShouldNot(HaveKeyWithValue("org.opencontainers.image.source", anSource))
}
By("checking config layer")
Expect(rulesfileData.Layer.Config.Version).Should(Equal(version))
Expect(rulesfileData.Layer.Config.Name).Should(Equal(artifactNameInConfigLayer))
if dependencies {
Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Dependencies[0].Name,
rulesfileData.Layer.Config.Dependencies[0].Version)).Should(Equal(dep1))
Expect(fmt.Sprintf("%s:%s|%s:%s", rulesfileData.Layer.Config.Dependencies[1].Name,
rulesfileData.Layer.Config.Dependencies[1].Version, rulesfileData.Layer.Config.Dependencies[1].Alternatives[0].Name,
rulesfileData.Layer.Config.Dependencies[1].Alternatives[0].Version)).Should(Equal(dep2))
} else {
Expect(rulesfileData.Layer.Config.Dependencies).Should(HaveLen(0))
}
if requirements {
Expect(fmt.Sprintf("%s:%s", rulesfileData.Layer.Config.Requirements[0].Name,
rulesfileData.Layer.Config.Requirements[0].Version)).Should(Equal(req))
} else {
Expect(rulesfileData.Layer.Config.Requirements).Should(HaveLen(0))
}
By("checking tags")
Expect(rulesfileData.Tags).Should(HaveLen(len(pushedTags)))
Expect(rulesfileData.Tags).Should(ContainElements(pushedTags))
})
}
// Here we are testing all the success cases for the push command. The artifact type used here is of type
// rulesfile. Keep in mind that here we are testing also the common flags that could be used by the plugin
// artifacts. So we are testing that common logic only once, and are doing it here.
commonFlagsAndRulesfileSpecificFlags := Context("rulesfiles and common flags", func() {
JustBeforeEach(func() {
// This runs after the push command, so check the returned error before proceeding.
Expect(err).ShouldNot(HaveOccurred())
rulesfileData, err = testutils.FetchRulesfileFromRegistry(ctx, repoName, pushedTags[0], orasRegistry)
Expect(err).ShouldNot(HaveOccurred())
})
JustAfterEach(func() {
// This variable could be changed by single tests.
// Make sure to set them at their default values.
artifactNameInConfigLayer = "test-rulesfile"
pushedTags = []string{"tag1", "tag2", "latest"}
})
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, rulesRepoBaseName)
})
When("with full flags and args", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour(true, true, true)
})
When("no --name flag provided", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2]}
// Set name to the expected one.
artifactNameInConfigLayer = repoName
})
AssertSuccesBehaviour(true, true, true)
})
When("no --annotation-source provided", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour(true, true, false)
})
When("no --tags provided", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
"--name", artifactNameInConfigLayer}
// We expect that latest tag is pushed, so set it in the pushed tags.
pushedTags = []string{"latest"}
})
AssertSuccesBehaviour(true, true, true)
})
When("no --depends-on flag provided", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour(false, true, true)
})
When("no --requires flag provided", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccesBehaviour(true, false, true)
})
When("only required flags", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http"}
// Set name to the expected one.
artifactNameInConfigLayer = repoName
// We expect that latest tag is pushed, so set it in the pushed tags.
pushedTags = []string{"latest"}
})
AssertSuccesBehaviour(false, false, false)
})
})
Context("rulesfile", func() {
Context("tar.gz format", func() {
rulesfile = rulesfiletgz
var _ = commonFlagsAndRulesfileSpecificFlags
})
Context("raw format", func() {
rulesfile = rulesfileyaml
// Push a raw rulesfiles using all the flags combinations.
var _ = commonFlagsAndRulesfileSpecificFlags
Context("filesystem cleanup", func() {
// Push a raw rulesfile.
BeforeEach(func() {
// Some values such as fullRepoName is the last one set by the other tests or the default one.
// Anyway we do not really care since the tar.gz is created before.
args = []string{registryCmd, pushCmd, fullRepoName, rulesfile, "--config", configFile, "--type", "rulesfile", "--version", version,
"--plain-http"}
})
It("temp dir should not exist", func() {
Expect(err).ShouldNot(HaveOccurred())
entries, err := os.ReadDir("/tmp")
Expect(err).ShouldNot(HaveOccurred())
for _, e := range entries {
if e.IsDir() {
matched, err := filepath.Match(utils.TmpDirPrefix+"*", regexp.QuoteMeta(e.Name()))
Expect(err).ShouldNot(HaveOccurred())
Expect(matched).ShouldNot(BeTrue())
}
}
})
})
})
})
// We keep it inside the success context since need the variables of this context.
var AssertSuccessBehaviourPlugins = func(dependencies, requirements, annotation bool) {
It("should succeed", func() {
// We do not check the error here since we are checking it before
// pulling the artifact.
By("checking no error in output")
Expect(output).ShouldNot(gbytes.Say("ERROR"))
Expect(output).ShouldNot(gbytes.Say("Unable to remove temporary dir"))
By("checking descriptor")
Expect(pluginData.Descriptor.MediaType).Should(Equal(v1.MediaTypeImageIndex))
Expect(output).Should(gbytes.Say(regexp.QuoteMeta(pluginData.Descriptor.Digest.String())))
By("checking index")
Expect(pluginData.Index.Manifests).Should(HaveLen(2))
if annotation {
Expect(pluginData.Index.Annotations).Should(HaveKeyWithValue("org.opencontainers.image.source", anSource))
} else {
Expect(pluginData.Descriptor.Annotations).ShouldNot(HaveKeyWithValue("org.opencontainers.image.source", anSource))
}
By("checking platforms")
Expect(pluginData.Platforms).Should(HaveKey(platformARM64))
Expect(pluginData.Platforms).Should(HaveKey(platformAMD64))
By("checking config layer")
for _, p := range pluginData.Platforms {
Expect(p.Config.Version).Should(Equal(version))
Expect(p.Config.Name).Should(Equal(artifactNameInConfigLayer))
if dependencies {
Expect(fmt.Sprintf("%s:%s", p.Config.Dependencies[0].Name, p.Config.Dependencies[0].Version)).Should(Equal(dep1))
Expect(fmt.Sprintf("%s:%s|%s:%s", p.Config.Dependencies[1].Name, p.Config.Dependencies[1].Version,
p.Config.Dependencies[1].Alternatives[0].Name, p.Config.Dependencies[1].Alternatives[0].Version)).Should(Equal(dep2))
} else {
Expect(p.Config.Dependencies).Should(HaveLen(0))
}
if requirements {
Expect(fmt.Sprintf("%s:%s", p.Config.Requirements[0].Name, p.Config.Requirements[0].Version)).Should(Equal(req))
} else {
Expect(p.Config.Requirements).Should(HaveLen(0))
}
}
By("checking tags")
Expect(pluginData.Tags).Should(HaveLen(len(pushedTags)))
Expect(pluginData.Tags).Should(ContainElements(pushedTags))
})
}
// Here we are testing the success cases for the push command using a plugin artifact and its related flags.
// Other flags related to the plugin artifacts are tested in the rulesfile artifact section.
PluginsSpecificFlags := Context("plugins specific flags", func() {
JustBeforeEach(func() {
// This runs after the push command, so check the returned error before proceeding.
Expect(err).ShouldNot(HaveOccurred())
pluginData, err = testutils.FetchPluginFromRegistry(ctx, repoName, pushedTags[0], orasRegistry)
Expect(err).ShouldNot(HaveOccurred())
})
JustAfterEach(func() {
// This variable could be changed by single tests.
// Make sure to set them at their default values.
artifactNameInConfigLayer = "test-plugin"
pushedTags = []string{"tag1", "tag2", "latest"}
})
BeforeEach(func() {
repoName, fullRepoName = randomRulesRepoName(registry, pluginsRepoBaseName)
})
When("with full flags and args", func() {
BeforeEach(func() {
args = []string{registryCmd, pushCmd, fullRepoName, plugin, pluginRaw, "--type", "plugin", "--platform",
platformAMD64, "--platform", platformARM64, "--version", version, "--config", configFile,
"--plain-http", "--depends-on", dep1, "--depends-on", dep2, "--requires", req, "--annotation-source", anSource,
"--tag", pushedTags[0], "--tag", pushedTags[1], "--tag", pushedTags[2], "--name", artifactNameInConfigLayer}
})
AssertSuccessBehaviourPlugins(true, true, true)
})
})
Context("plugin", func() {
Context("tar.gz + raw format format", func() {
plugin = plugintgz
// We do not really care what the file is.
pluginRaw = rulesfileyaml
var _ = PluginsSpecificFlags
})
})
})
}) })

View File

@ -44,7 +44,7 @@ Usage:
Available Commands: Available Commands:
artifact Interact with Falco artifacts artifact Interact with Falco artifacts
completion Generate the autocompletion script for the specified shell completion Generate the autocompletion script for the specified shell
driver Interact with falcosecurity driver driver [Preview] Interact with falcosecurity driver
help Help about any command help Help about any command
index Interact with index index Interact with index
registry Interact with OCI registries registry Interact with OCI registries

483
go.mod
View File

@ -1,76 +1,59 @@
module github.com/falcosecurity/falcoctl module github.com/falcosecurity/falcoctl
go 1.24.3 go 1.21
require ( require (
cloud.google.com/go/storage v1.51.0 cloud.google.com/go/storage v1.33.0
github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/config v1.29.9
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3
github.com/blang/semver v3.5.1+incompatible github.com/blang/semver v3.5.1+incompatible
github.com/blang/semver/v4 v4.0.0 github.com/distribution/distribution/v3 v3.0.0-20230608105614-4501a6e06d3b
github.com/cilium/ebpf v0.17.3 github.com/docker/cli v24.0.7+incompatible
github.com/distribution/distribution/v3 v3.0.0 github.com/docker/docker v24.0.7+incompatible
github.com/docker/cli v28.3.2+incompatible github.com/falcosecurity/driverkit v0.16.0
github.com/docker/docker v28.3.3+incompatible github.com/go-oauth2/oauth2/v4 v4.5.2
github.com/falcosecurity/driverkit v0.21.2 github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/go-oauth2/oauth2/v4 v4.5.3 github.com/google/go-containerregistry v0.16.1
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/go-containerregistry v0.20.3
github.com/gookit/color v1.5.4 github.com/gookit/color v1.5.4
github.com/mattn/go-isatty v0.0.20 github.com/mitchellh/mapstructure v1.5.0
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c github.com/onsi/ginkgo/v2 v2.10.0
github.com/onsi/ginkgo/v2 v2.23.3 github.com/onsi/gomega v1.27.8
github.com/onsi/gomega v1.36.3 github.com/opencontainers/image-spec v1.1.0-rc5
github.com/opencontainers/image-spec v1.1.1 github.com/oras-project/oras-credentials-go v0.3.0
github.com/pterm/pterm v0.12.80 github.com/pterm/pterm v0.12.67
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/sigstore/cosign/v2 v2.4.3 github.com/sigstore/cosign/v2 v2.2.1
github.com/sigstore/sigstore v1.9.1 github.com/sigstore/sigstore v1.7.5
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.1 github.com/spf13/cobra v1.8.0
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.1 github.com/spf13/pflag v1.0.5
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.1 github.com/spf13/viper v1.17.0
github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.1 golang.org/x/crypto v0.17.0
github.com/spf13/cobra v1.9.1 golang.org/x/exp v0.0.0-20231006140011-7918f672742d
github.com/spf13/pflag v1.0.6 google.golang.org/api v0.149.0
github.com/spf13/viper v1.20.0
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
golang.org/x/net v0.40.0
golang.org/x/oauth2 v0.28.0
golang.org/x/sys v0.33.0
golang.org/x/term v0.32.0
google.golang.org/api v0.227.0
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.32.3 k8s.io/apimachinery v0.28.3
k8s.io/apimachinery v0.32.3 k8s.io/client-go v0.28.3
k8s.io/client-go v0.32.3 k8s.io/utils v0.0.0-20230726121419-3b25d923346b
oras.land/oras-go/v2 v2.5.0 oras.land/oras-go/v2 v2.3.0
)
require (
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
golang.org/x/sync v0.5.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
) )
require ( require (
atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/cursor v0.2.0 // indirect
atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/keyboard v0.2.9 // indirect
atomicgo.dev/schedule v0.1.0 // indirect atomicgo.dev/schedule v0.1.0 // indirect
cel.dev/expr v0.19.2 // indirect cloud.google.com/go v0.110.9 // indirect
cloud.google.com/go v0.118.3 // indirect cloud.google.com/go/compute v1.23.2 // indirect
cloud.google.com/go/auth v0.15.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect cloud.google.com/go/iam v1.1.4 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect filippo.io/edwards25519 v1.0.0 // indirect
cloud.google.com/go/iam v1.4.1 // indirect github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect
cloud.google.com/go/kms v1.21.0 // indirect
cloud.google.com/go/longrunning v0.6.5 // indirect
cloud.google.com/go/monitoring v1.24.0 // indirect
github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
@ -79,13 +62,16 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect github.com/DataDog/appsec-internal-go v1.0.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.49.0-devel // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/DataDog/datadog-go/v5 v5.3.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/DataDog/go-libddwaf v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/DataDog/sketches-go v1.4.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20230117174420-439a4b8ba167 // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect
@ -94,211 +80,163 @@ require (
github.com/alibabacloud-go/debug v1.0.0 // indirect github.com/alibabacloud-go/debug v1.0.0 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
github.com/alibabacloud-go/tea v1.2.2 // indirect github.com/alibabacloud-go/tea v1.2.1 // indirect
github.com/alibabacloud-go/tea-utils v1.4.5 // indirect github.com/alibabacloud-go/tea-utils v1.4.5 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/credentials-go v1.3.3 // indirect github.com/aliyun/credentials-go v1.3.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect github.com/aws/aws-sdk-go-v2 v1.21.2 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.62 // indirect github.com/aws/aws-sdk-go-v2/config v1.19.1 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.43 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.40.3 // indirect github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.31.2 // indirect github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 // indirect github.com/aws/smithy-go v1.15.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bshuster-repo/logrus-logstash-hook v1.1.0 // indirect github.com/bshuster-repo/logrus-logstash-hook v1.1.0 // indirect
github.com/buildkite/agent/v3 v3.92.1 // indirect github.com/bugsnag/bugsnag-go v2.2.0+incompatible // indirect
github.com/buildkite/go-pipeline v0.13.3 // indirect github.com/bugsnag/bugsnag-go/v2 v2.2.0 // indirect
github.com/buildkite/interpolate v0.1.5 // indirect github.com/bugsnag/panicwrap v1.3.4 // indirect
github.com/buildkite/roko v1.3.1 // indirect github.com/buildkite/agent/v3 v3.58.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/gettext-go v1.0.3 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.3.5 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/console v1.0.4 // indirect github.com/containerd/console v1.0.3 // indirect
github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/coreos/go-oidc/v3 v3.7.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/coreos/go-oidc/v3 v3.12.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/creasty/defaults v1.7.0 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/digitorus/timestamp v0.0.0-20230902153158-687734543647 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/ebitengine/purego v0.5.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-errors/errors v1.5.1 // indirect github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect github.com/go-logr/logr v1.3.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.22.0 // indirect github.com/go-openapi/errors v0.20.4 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/loads v0.22.0 // indirect github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/runtime v0.28.0 // indirect github.com/go-openapi/runtime v0.26.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/validate v0.24.0 // indirect github.com/go-openapi/validate v0.22.1 // indirect
github.com/go-piv/piv-go/v2 v2.3.0 // indirect github.com/go-piv/piv-go v1.11.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect github.com/go-playground/validator/v10 v10.15.5 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.3 // indirect github.com/gomodule/redigo v1.8.9 // indirect
github.com/google/certificate-transparency-go v1.3.1 // indirect github.com/google/certificate-transparency-go v1.1.7 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-github/v55 v55.0.0 // indirect github.com/google/go-github/v55 v55.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
github.com/google/s2a-go v0.1.9 // indirect github.com/google/s2a-go v0.1.7 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gowebpki/jcs v1.0.1 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.6 // indirect
github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
github.com/hashicorp/vault/api v1.16.0 // indirect github.com/imdario/mergo v0.3.16 // indirect
github.com/in-toto/attestation v1.1.0 // indirect
github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/in-toto/in-toto-golang v0.9.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect
github.com/jellydator/ttlcache/v3 v3.3.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/klauspost/compress v1.17.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.4 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/jwx/v2 v2.0.16 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect
github.com/mozillazg/docker-credential-acr-helper v0.4.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect
github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid v1.3.1 // indirect
github.com/oleiade/reflections v1.1.0 // indirect github.com/oleiade/reflections v1.0.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/outcaste-io/ristretto v0.2.3 // indirect
github.com/pborman/uuid v1.2.1 // indirect github.com/pborman/uuid v1.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/philhofer/fwd v1.1.2 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_golang v1.17.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/redis/go-redis/v9 v9.7.3 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect
github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect github.com/segmentio/ksuid v1.0.4 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/sigstore/fulcio v1.6.6 // indirect github.com/sigstore/fulcio v1.4.3 // indirect
github.com/sigstore/protobuf-specs v0.4.0 // indirect github.com/sigstore/rekor v1.3.3 // indirect
github.com/sigstore/rekor v1.3.9 // indirect github.com/sigstore/timestamp-authority v1.2.0 // indirect
github.com/sigstore/sigstore-go v0.7.0 // indirect
github.com/sigstore/timestamp-authority v1.2.4 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cast v1.5.1 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/spiffe/go-spiffe/v2 v2.1.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/thales-e-security/pool v0.0.2 // indirect github.com/thales-e-security/pool v0.0.2 // indirect
github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/theupdateframework/go-tuf v0.6.1 // indirect
github.com/theupdateframework/go-tuf/v2 v2.0.2 // indirect
github.com/tidwall/btree v1.6.0 // indirect github.com/tidwall/btree v1.6.0 // indirect
github.com/tidwall/buntdb v1.3.0 // indirect github.com/tidwall/buntdb v1.3.0 // indirect
github.com/tidwall/gjson v1.16.0 // indirect github.com/tidwall/gjson v1.16.0 // indirect
@ -307,75 +245,60 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/rtred v0.1.2 // indirect github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/tinylib/msgp v1.1.8 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/transparency-dev/merkle v0.0.2 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect
github.com/valyala/fasthttp v1.50.0 // indirect github.com/vbatts/tar-split v0.11.5 // indirect
github.com/vbatts/tar-split v0.11.6 // indirect github.com/xanzy/go-gitlab v0.93.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/zeebo/errs v1.4.0 // indirect github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 // indirect
gitlab.com/gitlab-org/api/client-go v0.123.0 // indirect github.com/yvasiyarov/gorelic v0.0.7 // indirect
go.mongodb.org/mongo-driver v1.15.0 // indirect github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect github.com/zeebo/errs v1.3.0 // indirect
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect go.mongodb.org/mongo-driver v1.12.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 // indirect go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect go.step.sm/crypto v0.36.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect
go.opentelemetry.io/otel/log v0.8.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.starlark.net v0.0.0-20240507195648-35fe9f26b4bc // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.26.0 // indirect
golang.org/x/mod v0.23.0 // indirect go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect
golang.org/x/sync v0.14.0 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect
golang.org/x/text v0.25.0 // indirect golang.org/x/mod v0.13.0 // indirect
golang.org/x/time v0.11.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.30.0 // indirect golang.org/x/time v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect golang.org/x/tools v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/grpc v1.71.0 // indirect google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/protobuf v1.36.5 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/DataDog/dd-trace-go.v1 v1.56.1 // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect
k8s.io/cli-runtime v0.30.0 // indirect inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a // indirect
k8s.io/component-base v0.30.0 // indirect k8s.io/api v0.28.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/kubectl v0.30.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/release-utils v0.7.6 // indirect
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
modernc.org/libc v1.50.5 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/sqlite v1.29.9 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/kustomize/api v0.17.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.17.0 // indirect
sigs.k8s.io/release-utils v0.11.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect
) )
require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-isatty v0.0.20
github.com/stretchr/testify v1.8.4
golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.13.0
golang.org/x/sys v0.15.0
golang.org/x/term v0.15.0
)

1627
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -30,7 +30,6 @@ import (
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/falcosecurity/falcoctl/internal/utils"
drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type"
"github.com/falcosecurity/falcoctl/pkg/oci" "github.com/falcosecurity/falcoctl/pkg/oci"
) )
@ -190,7 +189,7 @@ type Install struct {
// Driver represents the internal driver configuration (with Type string). // Driver represents the internal driver configuration (with Type string).
type Driver struct { type Driver struct {
Type []string `mapstructure:"type"` Type string `mapstructure:"type"`
Name string `mapstructure:"name"` Name string `mapstructure:"name"`
Repos []string `mapstructure:"repos"` Repos []string `mapstructure:"repos"`
Version string `mapstructure:"version"` Version string `mapstructure:"version"`
@ -206,10 +205,9 @@ func init() {
DefaultIndex = Index{ DefaultIndex = Index{
Name: "falcosecurity", Name: "falcosecurity",
URL: "https://falcosecurity.github.io/falcoctl/index.yaml", URL: "https://falcosecurity.github.io/falcoctl/index.yaml",
Backend: "https",
} }
DefaultDriver = Driver{ DefaultDriver = Driver{
Type: []string{drivertype.TypeModernBpf, drivertype.TypeKmod, drivertype.TypeBpf}, Type: drivertype.TypeKmod,
Name: "falco", Name: "falco",
Repos: []string{"https://download.falco.org/driver"}, Repos: []string{"https://download.falco.org/driver"},
Version: "", Version: "",
@ -392,14 +390,8 @@ func basicAuthListHookFunc() mapstructure.DecodeHookFuncType {
return data, fmt.Errorf("not valid token %q", token) return data, fmt.Errorf("not valid token %q", token)
} }
// Allow to have the registry expressed as a ref, but actually extract it.
registry, err := utils.GetRegistryFromRef(values[0])
if err != nil {
registry = values[0]
}
auths[i] = BasicAuth{ auths[i] = BasicAuth{
Registry: registry, Registry: values[0],
User: values[1], User: values[1],
Password: values[2], Password: values[2],
} }
@ -562,20 +554,7 @@ func Installer() (Install, error) {
}, nil }, nil
} }
// DriverTypes retrieves the driver types of the config file. // DriverRepos retrieves the driver section of the config file.
func DriverTypes() ([]string, error) {
// manage driver.Type as ";" separated list.
types := viper.GetStringSlice(DriverTypeKey)
if len(types) == 1 { // in this case it might come from the env
if !SemicolonSeparatedRegexp.MatchString(types[0]) {
return types, fmt.Errorf("env variable not correctly set, should match %q, got %q", SemicolonSeparatedRegexp.String(), types[0])
}
types = strings.Split(types[0], ";")
}
return types, nil
}
// DriverRepos retrieves the driver repos of the config file.
func DriverRepos() ([]string, error) { func DriverRepos() ([]string, error) {
// manage driver.Repos as ";" separated list. // manage driver.Repos as ";" separated list.
repos := viper.GetStringSlice(DriverReposKey) repos := viper.GetStringSlice(DriverReposKey)

View File

@ -42,11 +42,6 @@ import (
sigs "github.com/sigstore/cosign/v2/pkg/signature" sigs "github.com/sigstore/cosign/v2/pkg/signature"
"github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature"
// Register the provider-specific plugins.
_ "github.com/sigstore/sigstore/pkg/signature/kms/aws"
_ "github.com/sigstore/sigstore/pkg/signature/kms/azure"
_ "github.com/sigstore/sigstore/pkg/signature/kms/gcp"
_ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault"
) )
// VerifyCommand verifies a signature on a supplied container image. // VerifyCommand verifies a signature on a supplied container image.

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2025 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -203,83 +203,52 @@ func (f *Follower) follow(ctx context.Context) {
return return
} }
// Move files to their destination // Install the artifacts if necessary.
if err := f.moveFiles(filePaths, dstDir); err != nil { for _, path := range filePaths {
baseName := filepath.Base(path)
f.logger.Debug("Installing file", f.logger.Args("followerName", f.ref, "fileName", baseName))
dstPath := filepath.Join(dstDir, baseName)
// Check if the file exists.
f.logger.Debug("Checking if file already exists", f.logger.Args("followerName", f.ref, "fileName", baseName, "directory", dstDir))
exists, err := utils.FileExists(dstPath)
if err != nil {
f.logger.Error("Unable to check existence for file", f.logger.Args("followerName", f.ref, "fileName", baseName, "reason", err.Error()))
return return
} }
f.logger.Info("Artifact correctly installed",
f.logger.Args("followerName", f.ref, "artifactName", f.ref, "type", res.Type, "digest", res.Digest, "directory", dstDir))
f.currentDigest = desc.Digest.String()
}
// moveFiles moves files from their temporary location to the destination directory.
// It preserves the directory structure relative to the temporary directory.
// For example, if a file is at "tmpDir/subdir/file.yaml", it will be moved to
// "dstDir/subdir/file.yaml". This ensures that files in subdirectories are moved
// correctly as individual files, not as entire directories.
func (f *Follower) moveFiles(filePaths []string, dstDir string) error {
// Install the artifacts if necessary.
for _, path := range filePaths {
// Get the relative path from the temporary directory to preserve directory structure
relPath, err := filepath.Rel(f.tmpDir, path)
if err != nil {
f.logger.Error("Unable to get relative path", f.logger.Args("followerName", f.ref, "path", path, "reason", err.Error()))
return err
}
dstPath := filepath.Join(dstDir, relPath)
// Ensure the parent directory exists
if err := os.MkdirAll(filepath.Dir(dstPath), 0o750); err != nil {
f.logger.Error("Unable to create destination directory", f.logger.Args(
"followerName", f.ref,
"directory", filepath.Dir(dstPath),
"reason", err.Error(),
))
return err
}
f.logger.Debug("Installing file", f.logger.Args("followerName", f.ref, "path", relPath))
// Check if the file exists.
f.logger.Debug("Checking if file already exists", f.logger.Args("followerName", f.ref, "path", relPath, "directory", dstDir))
exists, err := utils.FileExists(dstPath)
if err != nil {
f.logger.Error("Unable to check existence for file", f.logger.Args("followerName", f.ref, "path", relPath, "reason", err.Error()))
return err
}
if !exists { if !exists {
f.logger.Debug("Moving file", f.logger.Args("followerName", f.ref, "path", relPath, "destDirectory", dstDir)) f.logger.Debug("Moving file", f.logger.Args("followerName", f.ref, "fileName", baseName, "destDirectory", dstDir))
if err = utils.Move(path, dstPath); err != nil { if err = utils.Move(path, dstPath); err != nil {
f.logger.Error("Unable to move file", f.logger.Args("followerName", f.ref, "path", relPath, "destDirectory", dstDir, "reason", err.Error())) f.logger.Error("Unable to move file", f.logger.Args("followerName", f.ref, "fileName", baseName, "destDirectory", dstDir, "reason", err.Error()))
return err return
} }
f.logger.Debug("File correctly installed", f.logger.Args("followerName", f.ref, "path", path)) f.logger.Debug("File correctly installed", f.logger.Args("followerName", f.ref, "path", path))
// It's done, move to the next file. // It's done, move to the next file.
continue continue
} }
f.logger.Debug(fmt.Sprintf("file %q already exists in %q, checking if it is equal to the existing one", baseName, dstDir),
f.logger.Debug(fmt.Sprintf("file %q already exists in %q, checking if it is equal to the existing one", relPath, dstDir),
f.logger.Args("followerName", f.ref)) f.logger.Args("followerName", f.ref))
// Check if the files are equal. // Check if the files are equal.
eq, err := equal([]string{path, dstPath}) eq, err := equal([]string{path, dstPath})
if err != nil { if err != nil {
f.logger.Error("Unable to compare files", f.logger.Args("followerName", f.ref, "existingFile", dstPath, "reason", err.Error())) f.logger.Error("Unable to compare files", f.logger.Args("followerName", f.ref, "newFile", path, "existingFile", dstPath, "reason", err.Error()))
return err return
} }
if !eq { if !eq {
f.logger.Debug(fmt.Sprintf("Overwriting file %q with file %q", dstPath, path), f.logger.Args("followerName", f.ref)) f.logger.Debug(fmt.Sprintf("Overwriting file %q with file %q", dstPath, path), f.logger.Args("followerName", f.ref))
if err = utils.Move(path, dstPath); err != nil { if err = utils.Move(path, dstPath); err != nil {
f.logger.Error("Unable to overwrite file", f.logger.Args("followerName", f.ref, "existingFile", dstPath, "reason", err.Error())) f.logger.Error("Unable to overwrite file", f.logger.Args("followerName", f.ref, "existingFile", dstPath, "reason", err.Error()))
return err return
} }
} else { } else {
f.logger.Debug("The two file are equal, nothing to be done") f.logger.Debug("The two file are equal, nothing to be done")
} }
} }
return nil
f.logger.Info("Artifact correctly installed",
f.logger.Args("followerName", f.ref, "artifactName", f.ref, "type", res.Type, "digest", res.Digest, "directory", dstDir))
f.currentDigest = desc.Digest.String()
} }
// pull downloads, extracts, and installs the artifact. // pull downloads, extracts, and installs the artifact.
@ -322,7 +291,7 @@ func (f *Follower) pull(ctx context.Context) (filePaths []string, res *oci.Regis
} }
// Extract artifact and move it to its destination directory // Extract artifact and move it to its destination directory
filePaths, err = utils.ExtractTarGz(ctx, file, f.tmpDir, 0) filePaths, err = utils.ExtractTarGz(file, f.tmpDir, 0)
if err != nil { if err != nil {
return filePaths, res, fmt.Errorf("unable to extract %q to %q: %w", res.Filename, f.tmpDir, err) return filePaths, res, fmt.Errorf("unable to extract %q to %q: %w", res.Filename, f.tmpDir, err)
} }

View File

@ -1,305 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2025 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package follower
import (
"os"
"path/filepath"
"testing"
"github.com/pterm/pterm"
"github.com/stretchr/testify/assert"
"github.com/falcosecurity/falcoctl/pkg/oci"
"github.com/falcosecurity/falcoctl/pkg/output"
)
func TestCheckRequirements(t *testing.T) {
printer := output.NewPrinter(pterm.LogLevelDebug, pterm.LogFormatterJSON, os.Stdout)
type testArtifact struct {
conf *oci.ArtifactConfig
falcoVersions map[string]string
expectErr bool
testName string
}
var testArtifactConfigs = []testArtifact{
{
conf: &oci.ArtifactConfig{
Name: "my_rule",
Version: "0.1.0",
Requirements: []oci.ArtifactRequirement{{Name: "engine_version_semver", Version: "0.26.0"}},
},
falcoVersions: map[string]string{"engine_version_semver": "0.26.0", "engine_version": "26"},
expectErr: false,
testName: "New Falco with new rules with new semver engine version",
},
{
conf: &oci.ArtifactConfig{
Name: "my_rule",
Version: "0.1.0",
Requirements: []oci.ArtifactRequirement{{Name: "engine_version_semver", Version: "26"}},
},
falcoVersions: map[string]string{"engine_version_semver": "0.26.0", "engine_version": "26"},
expectErr: true,
testName: "New Falco with new rules with old int engine version",
},
{
conf: &oci.ArtifactConfig{
Name: "my_rule",
Version: "0.1.0",
Requirements: []oci.ArtifactRequirement{{Name: "engine_version_semver", Version: "0.26.0"}},
},
falcoVersions: map[string]string{"engine_version": "26"},
expectErr: true,
testName: "Old Falco with new rules with new semver engine version",
},
{
conf: &oci.ArtifactConfig{
Name: "my_rule",
Version: "0.1.0",
Requirements: []oci.ArtifactRequirement{{Name: "engine_version_semver", Version: "26"}},
},
falcoVersions: map[string]string{"engine_version": "26"},
expectErr: true,
testName: "Old Falco with new new rules with old int engine version",
},
{
conf: &oci.ArtifactConfig{
Name: "my_rule",
Version: "0.1.0",
Requirements: []oci.ArtifactRequirement{{Name: "engine_version", Version: "26"}},
},
falcoVersions: map[string]string{"engine_version_semver": "0.26.0", "engine_version": "26"},
expectErr: false,
testName: "New Falco with old rules with old int engine version",
},
{
conf: &oci.ArtifactConfig{
Name: "my_rule",
Version: "0.1.0",
Requirements: []oci.ArtifactRequirement{{Name: "engine_version", Version: "0.26.0"}},
},
falcoVersions: map[string]string{"engine_version_semver": "0.26.0", "engine_version": "26"},
expectErr: true,
testName: "New Falco with old rules with new semver engine version",
},
{
conf: &oci.ArtifactConfig{
Name: "my_rule",
Version: "0.1.0",
Requirements: []oci.ArtifactRequirement{{Name: "engine_version", Version: "26"}},
},
falcoVersions: map[string]string{"engine_version": "26"},
expectErr: false,
testName: "Old Falco with old rules with old int engine version",
},
{
conf: &oci.ArtifactConfig{
Name: "my_rule",
Version: "0.1.0",
Requirements: []oci.ArtifactRequirement{{Name: "engine_version", Version: "0.26.0"}},
},
falcoVersions: map[string]string{"engine_version": "26"},
expectErr: true,
testName: "Old Falco with old rules with new semver engine version",
},
}
for _, artConf := range testArtifactConfigs {
t.Run(artConf.testName, func(t *testing.T) {
config := Config{
FalcoVersions: artConf.falcoVersions,
}
f, err := New("ghcr.io/falcosecurity/rules/my_rule:0.1.0", printer, &config)
assert.NoError(t, err)
err = f.checkRequirements(artConf.conf)
if artConf.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestMoveFiles(t *testing.T) {
type testFile struct {
path string
content string
replace bool
}
tests := []struct {
name string
files []testFile
existing []testFile
}{
{
name: "basic file at root",
files: []testFile{
{
path: "file1.yaml",
content: "content1",
},
},
},
{
name: "file in subdirectory",
files: []testFile{
{
path: "subdir/file2.yaml",
content: "content2",
},
},
},
{
name: "multiple files in different directories",
files: []testFile{
{
path: "file1.yaml",
content: "content1",
},
{
path: "subdir/file2.yaml",
content: "content2",
},
{
path: "subdir/nested/file3.yaml",
content: "content3",
},
},
},
{
name: "existing file with identical content",
files: []testFile{
{
path: "file1.yaml",
content: "content1",
replace: false,
},
},
existing: []testFile{
{
path: "file1.yaml",
content: "content1",
},
},
},
{
name: "existing file with different content",
files: []testFile{
{
path: "file1.yaml",
content: "new content",
replace: true,
},
},
existing: []testFile{
{
path: "file1.yaml",
content: "old content",
},
},
},
{
name: "mix of new and existing files",
files: []testFile{
{
path: "file1.yaml",
content: "content1",
replace: false,
},
{
path: "subdir/file2.yaml",
content: "new content2",
replace: true,
},
},
existing: []testFile{
{
path: "file1.yaml",
content: "content1",
},
{
path: "subdir/file2.yaml",
content: "old content2",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "falcoctl-test-*")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
dstDir, err := os.MkdirTemp("", "falcoctl-dst-*")
assert.NoError(t, err)
defer os.RemoveAll(dstDir)
// Setup existing files
for _, ef := range tt.existing {
dstPath := filepath.Join(dstDir, ef.path)
err = os.MkdirAll(filepath.Dir(dstPath), 0o755)
assert.NoError(t, err)
err = os.WriteFile(dstPath, []byte(ef.content), 0o644)
assert.NoError(t, err)
}
f, err := New("test-registry/test-ref", output.NewPrinter(pterm.LogLevelDebug, pterm.LogFormatterJSON, os.Stdout), &Config{
RulesfilesDir: dstDir,
TmpDir: tmpDir,
})
assert.NoError(t, err)
var paths []string
for _, tf := range tt.files {
fullPath := filepath.Join(f.tmpDir, tf.path)
err = os.MkdirAll(filepath.Dir(fullPath), 0o755)
assert.NoError(t, err)
err = os.WriteFile(fullPath, []byte(tf.content), 0o644)
assert.NoError(t, err)
paths = append(paths, fullPath)
}
f.currentDigest = "test-digest"
err = f.moveFiles(paths, dstDir)
assert.NoError(t, err)
for _, tf := range tt.files {
dstPath := filepath.Join(dstDir, tf.path)
_, err = os.Stat(dstPath)
assert.NoError(t, err, "file should exist at %s", dstPath)
content, err := os.ReadFile(dstPath)
assert.NoError(t, err)
assert.Equal(t, tf.content, string(content), "file content should match at %s", dstPath)
// For files marked as replace=false, verify they have identical content with existing files
if !tf.replace {
for _, ef := range tt.existing {
if ef.path == tf.path {
assert.Equal(t, ef.content, string(content), "file content should not change when replace=false: %s", dstPath)
}
}
}
}
})
}
}

View File

@ -19,8 +19,8 @@ import (
"context" "context"
"fmt" "fmt"
credentials "github.com/oras-project/oras-credentials-go"
"oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/credentials"
"github.com/falcosecurity/falcoctl/pkg/oci/registry" "github.com/falcosecurity/falcoctl/pkg/oci/registry"
) )

View File

@ -28,7 +28,7 @@ import (
// Login checks if passed gcp credentials are correct. // Login checks if passed gcp credentials are correct.
func Login(ctx context.Context, reg string) error { func Login(ctx context.Context, reg string) error {
// Check that we can find a valid token source using GCE or ApplicationDefault. // Check that we can find a valid token source using GCE or ApplicationDefault.
ts, err := google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/cloud-platform") ts, err := google.DefaultTokenSource(ctx)
if err != nil { if err != nil {
return fmt.Errorf("wrong GCP token source, unable to find a valid source: %w", err) return fmt.Errorf("wrong GCP token source, unable to find a valid source: %w", err)
} }

View File

@ -18,9 +18,9 @@ package login
import ( import (
"context" "context"
credentials "github.com/oras-project/oras-credentials-go"
"golang.org/x/oauth2/clientcredentials" "golang.org/x/oauth2/clientcredentials"
"oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/credentials"
"github.com/falcosecurity/falcoctl/internal/config" "github.com/falcosecurity/falcoctl/internal/config"
"github.com/falcosecurity/falcoctl/internal/login/basic" "github.com/falcosecurity/falcoctl/internal/login/basic"

View File

@ -43,8 +43,6 @@ func Verify(ctx context.Context, ref string, signature *index.Signature) error {
CertOidcIssuer: signature.Cosign.CertificateOidcIssuer, CertOidcIssuer: signature.Cosign.CertificateOidcIssuer,
CertOidcIssuerRegexp: signature.Cosign.CertificateOidcIssuerRegexp, CertOidcIssuerRegexp: signature.Cosign.CertificateOidcIssuerRegexp,
}, },
KeyRef: signature.Cosign.KeyRef,
IgnoreTlog: signature.Cosign.IgnoreTlog,
} }
return v.DoVerify(ctx, []string{ref}) return v.DoVerify(ctx, []string{ref})
} }

View File

@ -28,16 +28,13 @@ import (
// TmpDirPrefix prefix used for the temporary directory where the tar.gz archives live before pushing // TmpDirPrefix prefix used for the temporary directory where the tar.gz archives live before pushing
// to the OCI registry. // to the OCI registry.
const TmpDirPrefix = "falcoctl-registry-push-" const TmpDirPrefix = "falcoctl-registry-push"
// CreateTarGzArchive compresses and saves in a tar archive the passed file. // CreateTarGzArchive compresses and saves in a tar archive the passed file.
func CreateTarGzArchive(dir, path string, stripComponents bool) (file string, err error) { func CreateTarGzArchive(path string) (file string, err error) {
cleanedPath := filepath.Clean(path) cleanedPath := filepath.Clean(path)
if dir == "" {
dir = TmpDirPrefix
}
// Create output file. // Create output file.
tmpDir, err := os.MkdirTemp("", dir) tmpDir, err := os.MkdirTemp("", TmpDirPrefix)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -96,13 +93,13 @@ func CreateTarGzArchive(dir, path string, stripComponents bool) (file string, er
return nil return nil
} }
return copyToTarGz(path, tw, info, stripComponents) return copyToTarGz(path, tw, info)
}) })
if err != nil { if err != nil {
return "", err return "", err
} }
} else { } else {
if err = copyToTarGz(path, tw, fInfo, stripComponents); err != nil { if err = copyToTarGz(path, tw, fInfo); err != nil {
return "", err return "", err
} }
} }
@ -110,17 +107,9 @@ func CreateTarGzArchive(dir, path string, stripComponents bool) (file string, er
return outFile.Name(), err return outFile.Name(), err
} }
func copyToTarGz(path string, tw *tar.Writer, info fs.FileInfo, stripComponents bool) error { func copyToTarGz(path string, tw *tar.Writer, info fs.FileInfo) error {
var headerName string
if stripComponents {
headerName = filepath.Base(path)
} else {
headerName = path
}
header := &tar.Header{ header := &tar.Header{
Name: headerName, Name: path,
Size: info.Size(), Size: info.Size(),
Mode: int64(info.Mode()), Mode: int64(info.Mode()),
Typeflag: tar.TypeReg, Typeflag: tar.TypeReg,

View File

@ -29,32 +29,31 @@ import (
const ( const (
filename1 = "file1" filename1 = "file1"
filename2 = "file2" filename2 = "file2"
tmpPrefix = "testCreateTarGzArchiveFile"
) )
func TestCreateTarGzArchiveFile(t *testing.T) { func TestCreateTarGzArchiveFile(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()
f1, err := os.Create(filepath.Join(dir, filename1)) f1, err := os.Create(filepath.Join(dir, filename1))
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatalf(err.Error())
} }
defer f1.Close() defer f1.Close()
tarball, err := CreateTarGzArchive(tmpPrefix, filepath.Join(dir, filename1), false) tarball, err := CreateTarGzArchive(filepath.Join(dir, filename1))
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatalf(err.Error())
} }
defer os.RemoveAll(filepath.Dir(tarball)) defer os.Remove(tarball)
file, err := os.Open(tarball) file, err := os.Open(tarball)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatalf(err.Error())
} }
paths, err := listHeaders(file) paths, err := listHeaders(file)
fmt.Println(paths) fmt.Println(paths)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatalf(err.Error())
} }
if len(paths) != 1 { if len(paths) != 1 {
@ -67,41 +66,6 @@ func TestCreateTarGzArchiveFile(t *testing.T) {
} }
} }
func TestCreateTarGzArchiveFileStripComponents(t *testing.T) {
dir := t.TempDir()
f1, err := os.Create(filepath.Join(dir, filename1))
if err != nil {
t.Fatal(err.Error())
}
defer f1.Close()
tarball, err := CreateTarGzArchive(tmpPrefix, filepath.Join(dir, filename1), true)
if err != nil {
t.Fatal(err.Error())
}
defer os.RemoveAll(filepath.Dir(tarball))
file, err := os.Open(tarball)
if err != nil {
t.Fatal(err.Error())
}
paths, err := listHeaders(file)
fmt.Println(paths)
if err != nil {
t.Fatal(err.Error())
}
if len(paths) != 1 {
t.Fatalf("Expected 1 path, got %d", len(paths))
}
base := paths[0]
if base != filename1 {
t.Errorf("Expected file1, got %s", base)
}
}
func TestCreateTarGzArchiveDir(t *testing.T) { func TestCreateTarGzArchiveDir(t *testing.T) {
// Test that we can compress directories // Test that we can compress directories
dir := t.TempDir() dir := t.TempDir()
@ -109,30 +73,30 @@ func TestCreateTarGzArchiveDir(t *testing.T) {
// add some files // add some files
f1, err := os.Create(filepath.Join(dir, filename1)) f1, err := os.Create(filepath.Join(dir, filename1))
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatalf(err.Error())
} }
defer f1.Close() defer f1.Close()
f2, err := os.Create(filepath.Join(dir, filename2)) f2, err := os.Create(filepath.Join(dir, filename2))
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatalf(err.Error())
} }
defer f2.Close() defer f2.Close()
tarball, err := CreateTarGzArchive(tmpPrefix, dir, false) tarball, err := CreateTarGzArchive(dir)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatalf(err.Error())
} }
defer os.RemoveAll(filepath.Dir(tarball)) defer os.Remove(tarball)
file, err := os.Open(tarball) file, err := os.Open(tarball)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatalf(err.Error())
} }
defer file.Close() defer file.Close()
paths, err := listHeaders(file) paths, err := listHeaders(file)
if err != nil { if err != nil {
t.Fatal(err.Error()) t.Fatalf(err.Error())
} }
if len(paths) != 3 { if len(paths) != 3 {

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2023 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"bufio"
"os"
"strings"
"golang.org/x/term"
"github.com/falcosecurity/falcoctl/pkg/output"
)
// GetCredentials is used to retrieve username and password from standard input.
func GetCredentials(p *output.Printer) (username, password string, err error) {
reader := bufio.NewReader(os.Stdin)
p.DefaultText.Print(p.FormatTitleAsLoggerInfo("Enter username:"))
username, err = reader.ReadString('\n')
if err != nil {
return "", "", err
}
p.Logger.Info("Enter password: ")
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", "", err
}
password = string(bytePassword)
return strings.TrimSpace(username), strings.TrimSpace(password), nil
}

View File

@ -24,30 +24,12 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"golang.org/x/net/context"
) )
type link struct {
Name string
Path string
}
// ExtractTarGz extracts a *.tar.gz compressed archive and moves its content to destDir. // ExtractTarGz extracts a *.tar.gz compressed archive and moves its content to destDir.
// Returns a slice containing the full path of the extracted files. // Returns a slice containing the full path of the extracted files.
func ExtractTarGz(ctx context.Context, gzipStream io.Reader, destDir string, stripPathComponents int) ([]string, error) { func ExtractTarGz(gzipStream io.Reader, destDir string, stripPathComponents int) ([]string, error) {
var ( var files []string
files []string
links []link
symlinks []link
err error
)
// We need an absolute path
destDir, err = filepath.Abs(destDir)
if err != nil {
return nil, err
}
uncompressedStream, err := gzip.NewReader(gzipStream) uncompressedStream, err := gzip.NewReader(gzipStream)
if err != nil { if err != nil {
@ -55,45 +37,34 @@ func ExtractTarGz(ctx context.Context, gzipStream io.Reader, destDir string, str
} }
tarReader := tar.NewReader(uncompressedStream) tarReader := tar.NewReader(uncompressedStream)
for {
select {
case <-ctx.Done():
return nil, errors.New("interrupted")
default:
}
for {
header, err := tarReader.Next() header, err := tarReader.Next()
if errors.Is(err, io.EOF) { if errors.Is(err, io.EOF) {
break break
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
if strings.Contains(header.Name, "..") { if strings.Contains(header.Name, "..") {
return nil, fmt.Errorf("not allowed relative path in tar archive") return nil, fmt.Errorf("not allowed relative path in tar archive")
} }
path := header.Name strippedName := stripComponents(header.Name, stripPathComponents)
if stripPathComponents > 0 {
path = stripComponents(path, stripPathComponents)
}
if path == "" {
continue
}
if path, err = safeConcat(destDir, filepath.Clean(path)); err != nil {
// Skip paths that would escape destDir
continue
}
info := header.FileInfo()
switch header.Typeflag { switch header.Typeflag {
case tar.TypeDir: case tar.TypeDir:
if err = os.MkdirAll(path, info.Mode()); err != nil { d := filepath.Join(destDir, strippedName)
if err = os.Mkdir(filepath.Clean(d), 0o750); err != nil {
return nil, err return nil, err
} }
files = append(files, d)
case tar.TypeReg: case tar.TypeReg:
outFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode()) f := filepath.Join(destDir, strippedName)
outFile, err := os.Create(filepath.Clean(f))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -105,47 +76,13 @@ func ExtractTarGz(ctx context.Context, gzipStream io.Reader, destDir string, str
if err = outFile.Close(); err != nil { if err = outFile.Close(); err != nil {
return nil, err return nil, err
} }
files = append(files, path) files = append(files, f)
case tar.TypeLink:
name := header.Linkname
if stripPathComponents > 0 {
name = stripComponents(name, stripPathComponents)
}
if name == "" {
continue
}
name = filepath.Join(destDir, filepath.Clean(name))
links = append(links, link{Path: path, Name: name})
case tar.TypeSymlink:
symlinks = append(symlinks, link{Path: path, Name: header.Linkname})
default: default:
return nil, fmt.Errorf("extractTarGz: uknown type: %b in %s", header.Typeflag, header.Name) return nil, fmt.Errorf("extractTarGz: uknown type: %b in %s", header.Typeflag, header.Name)
} }
} }
// Now we make another pass creating the links
for i := range links {
select {
case <-ctx.Done():
return nil, errors.New("interrupted")
default:
}
if err = os.Link(links[i].Name, links[i].Path); err != nil {
return nil, err
}
}
for i := range symlinks {
select {
case <-ctx.Done():
return nil, errors.New("interrupted")
default:
}
if err = os.Symlink(symlinks[i].Name, symlinks[i].Path); err != nil {
return nil, err
}
}
return files, nil return files, nil
} }
@ -153,22 +90,11 @@ func stripComponents(headerName string, stripComponents int) string {
if stripComponents == 0 { if stripComponents == 0 {
return headerName return headerName
} }
names := strings.Split(headerName, string(filepath.Separator)) names := strings.FieldsFunc(headerName, func(r rune) bool {
return r == os.PathSeparator
})
if len(names) < stripComponents { if len(names) < stripComponents {
return headerName return headerName
} }
return filepath.Clean(strings.Join(names[stripComponents:], string(filepath.Separator))) return filepath.Clean(strings.Join(names[stripComponents:], "/"))
}
// safeConcat concatenates destDir and name
// but returns an error if the resulting path points outside 'destDir'.
func safeConcat(destDir, name string) (string, error) {
res := filepath.Join(destDir, name)
if !strings.HasSuffix(destDir, string(os.PathSeparator)) {
destDir += string(os.PathSeparator)
}
if !strings.HasPrefix(res, destDir) {
return res, fmt.Errorf("unsafe path concatenation: '%s' with '%s'", destDir, name)
}
return res, nil
} }

View File

@ -1,210 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"archive/tar"
"compress/gzip"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
const (
srcDir = "./foo"
)
var (
files = []string{srcDir + "/example.txt", srcDir + "/test.txt", srcDir + "/bar/baz.txt"}
)
func createTarball(t *testing.T, tarballFilePath, srcDir string) {
file, err := os.Create(tarballFilePath)
assert.NoError(t, err)
defer file.Close()
gzipWriter := gzip.NewWriter(file)
defer gzipWriter.Close()
tarWriter := tar.NewWriter(gzipWriter)
defer tarWriter.Close()
err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
return addToArchive(tarWriter, path, info)
})
assert.NoError(t, err)
}
func addToArchive(tw *tar.Writer, fullName string, info os.FileInfo) error {
// Open the file which will be written into the archive
file, err := os.Open(fullName)
if err != nil {
return err
}
defer file.Close()
// Create a tar Header from the FileInfo data
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
// Use full path as name (FileInfoHeader only takes the basename)
// If we don't do this the directory strucuture would
// not be preserved
// https://golang.org/src/archive/tar/common.go?#L626
header.Name = fullName
// Write file header to the tar archive
err = tw.WriteHeader(header)
if err != nil {
return err
}
if !info.IsDir() {
// Copy file content to tar archive
_, err = io.Copy(tw, file)
if err != nil {
return err
}
}
return nil
}
func TestExtractTarGz(t *testing.T) {
// Create src dir
err := os.MkdirAll(srcDir, 0o750)
assert.NoError(t, err)
t.Cleanup(func() {
_ = os.RemoveAll(srcDir)
})
// Generate files to be tarballed
for _, f := range files {
err := os.MkdirAll(filepath.Dir(f), 0o755)
assert.NoError(t, err)
_, err = os.Create(f)
assert.NoError(t, err)
}
// create tarball
createTarball(t, "./test.tgz", srcDir)
t.Cleanup(func() {
_ = os.RemoveAll("./test.tgz")
})
// Create dest folder
destDir := "./test"
err = os.MkdirAll(destDir, 0o750)
assert.NoError(t, err)
t.Cleanup(func() {
_ = os.RemoveAll(destDir)
})
// Extract tarball
f, err := os.Open("./test.tgz")
assert.NoError(t, err)
t.Cleanup(func() {
f.Close()
})
list, err := ExtractTarGz(context.TODO(), f, destDir, 0)
assert.NoError(t, err)
// Final checks
assert.NotEmpty(t, list)
// All extracted files are ok
for _, f := range list {
_, err := os.Stat(f)
assert.NoError(t, err)
}
// Extracted folder contains all source files (plus folders)
absDestDir, err := filepath.Abs(destDir)
assert.NoError(t, err)
for _, f := range files {
path := filepath.Join(absDestDir, f)
assert.Contains(t, list, path)
}
}
func TestExtractTarGzStripComponents(t *testing.T) {
// Create src dir
srcDir := "./foo"
err := os.MkdirAll(srcDir, 0o750)
assert.NoError(t, err)
t.Cleanup(func() {
_ = os.RemoveAll(srcDir)
})
// Generate files to be tarballed
for _, f := range files {
err := os.MkdirAll(filepath.Dir(f), 0o755)
assert.NoError(t, err)
_, err = os.Create(f)
assert.NoError(t, err)
}
// create tarball
createTarball(t, "./test.tgz", srcDir)
t.Cleanup(func() {
_ = os.RemoveAll("./test.tgz")
})
// Create dest folder
destdirStrip := "./test_strip"
err = os.MkdirAll(destdirStrip, 0o750)
assert.NoError(t, err)
t.Cleanup(func() {
_ = os.RemoveAll(destdirStrip)
})
// Extract tarball
f, err := os.Open("./test.tgz")
assert.NoError(t, err)
t.Cleanup(func() {
f.Close()
})
// NOTE that here we strip first component
list, err := ExtractTarGz(context.TODO(), f, destdirStrip, 1)
assert.NoError(t, err)
// Final checks
assert.NotEmpty(t, list)
// All extracted files are ok
for _, f := range list {
_, err := os.Stat(f)
assert.NoError(t, err)
}
// Extracted folder contains all source files (plus folders)
absDestDirStrip, err := filepath.Abs(destdirStrip)
assert.NoError(t, err)
for _, f := range files {
// We stripped first component (ie: srcDir)
ff := strings.TrimPrefix(f, srcDir)
path := filepath.Join(absDestDirStrip, ff)
assert.Contains(t, list, path)
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ package driverdistro
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"github.com/blang/semver" "github.com/blang/semver"
"github.com/falcosecurity/driverkit/pkg/kernelrelease" "github.com/falcosecurity/driverkit/pkg/kernelrelease"
@ -69,15 +68,15 @@ func (c *cos) customizeBuild(ctx context.Context,
} }
printer.Logger.Info("COS detected, using COS kernel headers.", printer.Logger.Args("build ID", c.buildID)) printer.Logger.Info("COS detected, using COS kernel headers.", printer.Logger.Args("build ID", c.buildID))
bpfKernelSrcURL := fmt.Sprintf("https://storage.googleapis.com/cos-tools/%s/kernel-headers.tgz", c.buildID) bpfKernelSrcURL := fmt.Sprintf("https://storage.googleapis.com/cos-tools/%s/kernel-headers.tgz", c.buildID)
kr.Extraversion = "+"
env, err := downloadKernelSrc(ctx, printer, &kr, bpfKernelSrcURL, 0) env, err := downloadKernelSrc(ctx, printer, &kr, bpfKernelSrcURL, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
currKernelDir := env[drivertype.KernelDirEnv] currKernelDir := env[kernelDirEnv]
cosKernelDir := filepath.Join(currKernelDir, "usr", "src") cosKernelDir := currKernelDir + "usr/src/"
entries, err := os.ReadDir(cosKernelDir) entries, err := os.ReadDir(cosKernelDir)
if err != nil { if err != nil {
return nil, err return nil, err
@ -85,9 +84,9 @@ func (c *cos) customizeBuild(ctx context.Context,
if len(entries) == 0 { if len(entries) == 0 {
return nil, fmt.Errorf("no COS kernel src found") return nil, fmt.Errorf("no COS kernel src found")
} }
cosKernelDir = filepath.Join(cosKernelDir, entries[0].Name()) cosKernelDir = entries[0].Name()
// Override env key // Override env key
env[drivertype.KernelDirEnv] = cosKernelDir env[kernelDirEnv] = cosKernelDir
clangCompilerHeader := fmt.Sprintf("%s/include/linux/compiler-clang.h", cosKernelDir) clangCompilerHeader := fmt.Sprintf("%s/include/linux/compiler-clang.h", cosKernelDir)
err = utils.ReplaceLineInFile(clangCompilerHeader, "#define randomized_struct_fields_start", "", 1) err = utils.ReplaceLineInFile(clangCompilerHeader, "#define randomized_struct_fields_start", "", 1)
@ -112,18 +111,3 @@ func (c *cos) customizeBuild(ctx context.Context,
} }
return env, nil return env, nil
} }
// PreferredDriver is reimplemented since COS does not support kmod
//
//nolint:gocritic // the method shall not be able to modify kr
func (c *cos) PreferredDriver(kr kernelrelease.KernelRelease, allowedDriverTypes []drivertype.DriverType) drivertype.DriverType {
for _, allowedDrvType := range allowedDriverTypes {
if allowedDrvType.String() == drivertype.TypeKmod {
continue
}
if allowedDrvType.Supported(kr) {
return allowedDrvType
}
}
return nil
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -17,7 +17,7 @@
package driverdistro package driverdistro
import ( import (
"compress/gzip" "archive/tar"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -29,8 +29,6 @@ import (
"strings" "strings"
"github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/homedir"
"github.com/falcosecurity/driverkit/cmd"
"github.com/falcosecurity/driverkit/pkg/driverbuilder"
"github.com/falcosecurity/driverkit/pkg/kernelrelease" "github.com/falcosecurity/driverkit/pkg/kernelrelease"
"golang.org/x/net/context" "golang.org/x/net/context"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
@ -41,9 +39,9 @@ import (
) )
const ( const (
// DefaultFalcoRepo is the default repository provided by falcosecurity to download driver artifacts from.
kernelDirEnv = "KERNELDIR"
kernelSrcDownloadFolder = "kernel-sources" kernelSrcDownloadFolder = "kernel-sources"
// UndeterminedDistro is the string used for the generic distro object returned when we cannot determine the distro.
UndeterminedDistro = "undetermined"
) )
var ( var (
@ -62,7 +60,7 @@ type Distro interface {
FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease // private FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease // private
customizeBuild(ctx context.Context, printer *output.Printer, driverType drivertype.DriverType, customizeBuild(ctx context.Context, printer *output.Printer, driverType drivertype.DriverType,
kr kernelrelease.KernelRelease) (map[string]string, error) kr kernelrelease.KernelRelease) (map[string]string, error)
PreferredDriver(kr kernelrelease.KernelRelease, allowedDriverTypes []drivertype.DriverType) drivertype.DriverType PreferredDriver(kr kernelrelease.KernelRelease) drivertype.DriverType
fmt.Stringer fmt.Stringer
} }
@ -95,7 +93,7 @@ func Discover(kr kernelrelease.KernelRelease, hostroot string) (Distro, error) {
// Return a generic distro to try the build // Return a generic distro to try the build
distro = &generic{} distro = &generic{}
if err = distro.init(kr, UndeterminedDistro, nil); err != nil { if err = distro.init(kr, "undetermined", nil); err != nil {
return nil, err return nil, err
} }
return distro, ErrUnsupported return distro, ErrUnsupported
@ -108,7 +106,7 @@ func getOSReleaseDistro(kr *kernelrelease.KernelRelease) (Distro, error) {
} }
idKey, err := cfg.Section("").GetKey("ID") idKey, err := cfg.Section("").GetKey("ID")
if err != nil { if err != nil {
return nil, err return nil, nil
} }
id := strings.ToLower(idKey.String()) id := strings.ToLower(idKey.String())
@ -169,68 +167,30 @@ func Build(ctx context.Context,
driverName string, driverName string,
driverType drivertype.DriverType, driverType drivertype.DriverType,
driverVer string, driverVer string,
downloadHeaders bool,
) (string, error) { ) (string, error) {
printer.Logger.Info("Trying to compile the requested driver")
driverFileName := toFilename(d, &kr, driverName, driverType) driverFileName := toFilename(d, &kr, driverName, driverType)
destPath := toLocalPath(driverVer, driverFileName, kr.Architecture.ToNonDeb()) destination := toLocalPath(driverVer, driverFileName, kr.Architecture.ToNonDeb())
if exist, _ := utils.FileExists(destPath); exist { if exist, _ := utils.FileExists(destination); exist {
return destPath, ErrAlreadyPresent return destination, ErrAlreadyPresent
} }
env, err := d.customizeBuild(ctx, printer, driverType, kr) env, err := d.customizeBuild(ctx, printer, driverType, kr)
if err != nil { if err != nil {
return "", err return "", err
} }
path, err := driverType.Build(ctx, printer, kr, driverName, driverVer, env)
ro, err := getDKRootOptions(d, kr, driverType, driverVer, driverName, destPath)
if err != nil { if err != nil {
return "", err return "", err
} }
// Copy the path to the expected location.
// Disable automatic kernel headers fetching // NOTE: for kmod, this is not useful since the driver will
// if customizeBuild already retrieved kernel headers for us // be loaded directly by dkms.
// (and has set the KernelDirEnv key) printer.Logger.Info("Copying built driver to its destination.", printer.Logger.Args("src", path, "dst", destination))
if _, ok := env[drivertype.KernelDirEnv]; ok { f, err := os.Open(filepath.Clean(path))
downloadHeaders = false
}
srcPath := fmt.Sprintf("/usr/src/%s-%s", driverName, driverVer)
err = driverbuilder.NewLocalBuildProcessor(true, downloadHeaders, true, srcPath, env, 1000).Start(ro.ToBuild(printer))
return destPath, err
}
//nolint:gocritic // the method shall not be able to modify kr
func getDKRootOptions(d Distro,
kr kernelrelease.KernelRelease,
driverType drivertype.DriverType,
driverVer,
driverName,
destPath string,
) (*cmd.RootOptions, error) {
ro, err := cmd.NewRootOptions()
if err != nil { if err != nil {
return nil, err return "", err
} }
ro.Architecture = kr.Architecture.String() return destination, copyDataToLocalPath(destination, f)
ro.DriverVersion = driverVer
// We pass just the fixed kernelversion down to driverkit.
// it is only used by ubuntu builder,
// all the other builders do not use the kernelversion field.
fixedKr := d.FixupKernel(kr)
ro.KernelVersion = fixedKr.KernelVersion
ro.ModuleDriverName = driverName
ro.ModuleDeviceName = driverName
ro.KernelRelease = kr.String()
ro.Target = d.String()
ro.Output = driverType.ToOutput(destPath)
// This should never happen since both kmod and bpf implement ToOutput;
// the only case this can happen is if a Build is requested for modern-bpf driver type,
// But "install" cmd is smart enough to avoid that situation
// by using HasArtifacts() method.
if !ro.Output.HasOutputs() {
return nil, errors.New("build on non-artifacts driver attempted")
}
return ro, nil
} }
// Download will try to download drivers for a distro trying specified repos. // Download will try to download drivers for a distro trying specified repos.
@ -243,7 +203,6 @@ func Download(ctx context.Context,
driverName string, driverName string,
driverType drivertype.DriverType, driverType drivertype.DriverType,
driverVer string, repos []string, driverVer string, repos []string,
httpHeaders string,
) (string, error) { ) (string, error) {
driverFileName := toFilename(d, &kr, driverName, driverType) driverFileName := toFilename(d, &kr, driverName, driverType)
// Skip if existent // Skip if existent
@ -255,25 +214,14 @@ func Download(ctx context.Context,
// Try to download from any specified repository, // Try to download from any specified repository,
// stopping at first successful http GET. // stopping at first successful http GET.
for _, repo := range repos { for _, repo := range repos {
driverURL := toURL(repo, driverVer, driverFileName, kr.Architecture.ToNonDeb()) url := toURL(repo, driverVer, driverFileName, kr.Architecture.ToNonDeb())
printer.Logger.Info("Trying to download a driver.", printer.Logger.Args("url", driverURL)) printer.Logger.Info("Trying to download a driver.", printer.Logger.Args("url", url))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, driverURL, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
printer.Logger.Warn("Error creating http request.", printer.Logger.Args("err", err)) printer.Logger.Warn("Error creating http request.", printer.Logger.Args("err", err))
continue continue
} }
if httpHeaders != "" {
header := http.Header{}
for _, h := range strings.Split(httpHeaders, ",") {
key, value := func() (string, string) {
x := strings.Split(h, ":")
return x[0], x[1]
}()
header.Add(key, value)
}
req.Header = header
}
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != 200 { if err != nil || resp.StatusCode != 200 {
if err == nil { if err == nil {
@ -371,7 +319,7 @@ func downloadKernelSrc(ctx context.Context,
return env, err return env, err
} }
_, err = utils.ExtractTarGz(ctx, resp.Body, fullKernelDir, stripComponents) _, err = utils.ExtractTarGz(resp.Body, fullKernelDir, stripComponents)
if err != nil { if err != nil {
return env, err return env, err
} }
@ -388,17 +336,12 @@ func downloadKernelSrc(ctx context.Context,
if err != nil { if err != nil {
return nil, err return nil, err
} }
var src io.ReadCloser var src io.Reader
if strings.HasSuffix(kernelConfigPath, ".gz") { if strings.HasSuffix(kernelConfigPath, ".gz") {
src, err = gzip.NewReader(f) src = tar.NewReader(f)
if err != nil {
return env, err
}
} else { } else {
src = f src = f
} }
defer src.Close()
fStat, err := f.Stat() fStat, err := f.Stat()
if err != nil { if err != nil {
return nil, err return nil, err
@ -407,6 +350,6 @@ func downloadKernelSrc(ctx context.Context,
if err != nil { if err != nil {
return nil, err return nil, err
} }
env[drivertype.KernelDirEnv] = fullKernelDir env[kernelDirEnv] = fullKernelDir
return env, nil return env, nil
} }

View File

@ -50,7 +50,7 @@ func TestDiscoverDistro(t *testing.T) {
return nil return nil
}, },
postFn: func() {}, postFn: func() {},
distroExpected: &generic{}, distroExpected: nil,
errExpected: true, errExpected: true,
}, },
{ {
@ -63,8 +63,8 @@ func TestDiscoverDistro(t *testing.T) {
postFn: func() { postFn: func() {
_ = os.Remove(osReleaseFile) _ = os.Remove(osReleaseFile)
}, },
distroExpected: &generic{}, distroExpected: nil,
errExpected: true, errExpected: false,
}, },
{ {
// os-release ID "foo" mapped to generic // os-release ID "foo" mapped to generic
@ -167,28 +167,6 @@ func TestDiscoverDistro(t *testing.T) {
distroExpected: &bottlerocket{}, distroExpected: &bottlerocket{},
errExpected: false, errExpected: false,
}, },
{
// os-release ID "ol" maps to oracle
krInput: "5.10.0-2047.510.5.5.el7uek.x86_64",
preFn: func() error {
type brCfg struct {
OsID string `ini:"ID"`
}
f := ini.Empty()
err := f.ReflectFrom(&brCfg{
OsID: "ol",
})
if err != nil {
return err
}
return f.SaveTo(osReleaseFile)
},
postFn: func() {
_ = os.Remove(osReleaseFile)
},
distroExpected: &ol{},
errExpected: false,
},
{ {
// No os-release but "centos-release" file present maps to centos // No os-release but "centos-release" file present maps to centos
krInput: "5.10.0", krInput: "5.10.0",
@ -228,8 +206,9 @@ func TestDiscoverDistro(t *testing.T) {
d, err := Discover(kr, localHostRoot) d, err := Discover(kr, localHostRoot)
if tCase.errExpected { if tCase.errExpected {
assert.Error(t, err) assert.Error(t, err)
} } else {
assert.IsType(t, tCase.distroExpected, d) assert.IsType(t, tCase.distroExpected, d)
}
tCase.postFn() tCase.postFn()
} }
} }

View File

@ -28,57 +28,29 @@ import (
) )
const flatcarRelocateScript = ` const flatcarRelocateScript = `
set -euo pipefail local -a tools=(
shopt -s nullglob
hostlds=( /host/usr/lib64/ld-linux-*.so.* )
if [[ ${#hostlds[@]} -eq 0 ]]; then
echo "** no dynamic loaders found"
exit 1
fi
if [[ ${#hostlds[@]} -gt 1 ]]; then
echo "** more than one fitting dynamic loader found, picking first"
fi
hostld=${hostlds[0]}
echo "** Found host dynamic loader: ${hostld}"
kdirs=( /host/lib/modules/*/build )
if [[ ${#kdirs[@]} -eq 0 ]]; then
echo "** no kernel module tools directories found"
exit 1
fi
if [[ ${#kdirs[@]} -gt 1 ]]; then
echo "** more than one fitting kernel module tools directory found, picking first"
fi
kdir=${kdirs[0]}
echo "** Found kernel tools directory: ${kdir}"
tools=(
scripts/basic/fixdep scripts/basic/fixdep
scripts/mod/modpost scripts/mod/modpost
tools/objtool/objtool tools/objtool/objtool
) )
local -r hostld=$(ls /host/usr/lib64/ld-linux-*.so.*)
tmp_dir=$(mktemp -d) local -r kdir=/lib/modules/$(ls /lib/modules/)/build
for tool in "${tools[@]}"; do echo "** Found host dl interpreter: ${hostld}"
host_tool=${kdir}/${tool} for host_tool in ${tools[@]}; do
if [[ ! -f ${host_tool} ]]; then t=${host_tool}
echo "${tool@Q} not found in ${kdir@Q}, not patching" tool=$(basename $t)
tool_dir=$(dirname $t)
host_tool=${kdir}/${host_tool}
if [ ! -f ${host_tool} ]; then
continue continue
fi fi
umount "${host_tool}" 2>/dev/null || true umount ${host_tool} 2>/dev/null || true
tmp_tool=${tmp_dir}/${tool} mkdir -p /tmp/${tool_dir}/
mkdir -p "$(dirname "${tmp_tool}")" cp -a ${host_tool} /tmp/${tool_dir}/
cp -a "${host_tool}" "${tmp_tool}" echo "** Setting host dl interpreter for $host_tool"
echo "** Setting host dynamic loader for ${tool@Q}" patchelf --set-interpreter ${hostld} --set-rpath /host/usr/lib64 /tmp/${tool_dir}/${tool}
patchelf \ mount -o bind /tmp/${tool_dir}/${tool} ${host_tool}
--set-interpreter "${hostld}" \
--set-rpath /host/usr/lib64 \
"${tmp_tool}"
mount -o bind "${tmp_tool}" "${host_tool}"
done done
rm -rf "${tmp_dir}"
` `
func init() { func init() {
@ -122,7 +94,7 @@ func (f *flatcar) customizeBuild(ctx context.Context,
return nil, nil return nil, nil
} }
printer.Logger.Info("Flatcar detected; relocating kernel tools.", printer.Logger.Args("version", f.versionID)) printer.Logger.Info("Flatcar detected; relocating kernel tools.", printer.Logger.Args("version", f.versionID))
out, err := exec.CommandContext(ctx, "/bin/bash", "-c", flatcarRelocateScript).CombinedOutput() out, err := exec.CommandContext(ctx, "/bin/bash", "-c", flatcarRelocateScript).Output()
if err != nil { if err != nil {
printer.DefaultText.Print(string(out)) printer.DefaultText.Print(string(out))
} }

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -16,8 +16,9 @@
package driverdistro package driverdistro
import ( import (
"regexp" "strings"
"github.com/blang/semver"
"github.com/falcosecurity/driverkit/pkg/kernelrelease" "github.com/falcosecurity/driverkit/pkg/kernelrelease"
"golang.org/x/net/context" "golang.org/x/net/context"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
@ -26,14 +27,6 @@ import (
"github.com/falcosecurity/falcoctl/pkg/output" "github.com/falcosecurity/falcoctl/pkg/output"
) )
// Parse start of string as "#NUMBER":
// eg1: "#1 SMP PREEMPT_DYNAMIC Tue, 10 Oct 2023 21:10:21 +0000" -> "1".
// eg2: #1-photon -> "1"
// Old falco-driver-loader method did:
// echo "${DRIVER_KERNEL_VERSION}" | sed 's/#\([[:digit:]]\+\).*/\1/'
// The regex does the same thing.
var genericKernelVersionRegex = regexp.MustCompile(`#(\d+).*`)
type generic struct { type generic struct {
targetID string targetID string
} }
@ -50,10 +43,10 @@ func (g *generic) String() string {
//nolint:gocritic // the method shall not be able to modify kr //nolint:gocritic // the method shall not be able to modify kr
func (g *generic) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease { func (g *generic) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.KernelRelease {
matches := genericKernelVersionRegex.FindStringSubmatch(kr.KernelVersion) // Take eg: "#1 SMP PREEMPT_DYNAMIC Tue, 10 Oct 2023 21:10:21 +0000" and return "1".
if len(matches) == 2 { kv := strings.TrimLeft(kr.KernelVersion, "#")
kr.KernelVersion = matches[1] kv = strings.Split(kv, " ")[0]
} kr.KernelVersion = kv
return kr return kr
} }
@ -67,11 +60,16 @@ func (g *generic) customizeBuild(_ context.Context,
} }
//nolint:gocritic // the method shall not be able to modify kr //nolint:gocritic // the method shall not be able to modify kr
func (g *generic) PreferredDriver(kr kernelrelease.KernelRelease, allowedDriverTypes []drivertype.DriverType) drivertype.DriverType { func (g *generic) PreferredDriver(kr kernelrelease.KernelRelease) drivertype.DriverType {
for _, allowedDrvType := range allowedDriverTypes { // Deadly simple default automatic selection
if allowedDrvType.Supported(kr) { var dType drivertype.DriverType
return allowedDrvType switch {
case kr.GTE(semver.MustParse("5.8.0")):
dType, _ = drivertype.Parse(drivertype.TypeModernBpf)
case kr.SupportsProbe():
dType, _ = drivertype.Parse(drivertype.TypeBpf)
default:
dType, _ = drivertype.Parse(drivertype.TypeKmod)
} }
} return dType
return nil
} }

View File

@ -1,61 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package driverdistro
import (
"testing"
"github.com/falcosecurity/driverkit/pkg/kernelrelease"
"github.com/stretchr/testify/assert"
)
func TestDistroGeneric(t *testing.T) {
type testCase struct {
krInput string
kvInput string
kvExpected string
}
testCases := []testCase{
{
krInput: "4.19.283-3.ph3",
kvInput: "#1-photon SMP Fri Jun 16 02:25:27 UTC 2023",
kvExpected: "1",
},
{
krInput: "6.7.2-arch1-2",
kvInput: "#1 SMP PREEMPT_DYNAMIC Wed, 31 Jan 2024 09:22:15 +0000",
kvExpected: "1",
},
{
krInput: "6.7.2-arch1-2",
kvInput: "#231asfa #rf3f",
kvExpected: "231",
},
{
krInput: "6.7.2-arch1-2",
kvInput: "#231asfa234",
kvExpected: "231",
},
}
g := &generic{}
for _, tCase := range testCases {
kr := kernelrelease.FromString(tCase.krInput)
kr.KernelVersion = tCase.kvInput
fixedKr := g.FixupKernel(kr)
assert.Equal(t, tCase.kvExpected, fixedKr.KernelVersion)
}
}

View File

@ -1,46 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package driverdistro
import (
"strings"
"github.com/falcosecurity/driverkit/pkg/kernelrelease"
drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type"
)
func init() {
distros["ol"] = &ol{generic: &generic{}}
}
type ol struct {
*generic
}
//nolint:gocritic // the method shall not be able to modify kr
func (o *ol) PreferredDriver(kr kernelrelease.KernelRelease, allowedDriverTypes []drivertype.DriverType) drivertype.DriverType {
for _, allowedDrvType := range allowedDriverTypes {
// Skip dkms on UEK hosts because it will always fail
if allowedDrvType.String() == drivertype.TypeKmod && strings.Contains(kr.String(), "uek") {
continue
}
if allowedDrvType.Supported(kr) {
return allowedDrvType
}
}
return nil
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -54,9 +54,12 @@ func (u *ubuntu) FixupKernel(kr kernelrelease.KernelRelease) kernelrelease.Kerne
// from the following `uname -v` result // from the following `uname -v` result
// `#26~22.04.1-Ubuntu SMP Mon Apr 24 01:58:15 UTC 2023` // `#26~22.04.1-Ubuntu SMP Mon Apr 24 01:58:15 UTC 2023`
// we obtain the kernelversion`26~22.04.1`. // we obtain the kernelversion`26~22.04.1`.
// Another example: "#1 SMP PREEMPT_DYNAMIC Tue, 10 Oct 2023 21:10:21 +0000" and return "1". // NOTE: driverkernel.FetchInfo() already trims leading "#"
kv := strings.TrimLeft(kr.KernelVersion, "#") // and everything starting from the first whitespace,
kv = strings.Split(kv, " ")[0] // so that eg: we receive "26~22.04.1-Ubuntu",
kr.KernelVersion = strings.TrimSuffix(kv, "-Ubuntu") // therefore we only need to drop "-Ubuntu" suffix
// Take eg: "#1 SMP PREEMPT_DYNAMIC Tue, 10 Oct 2023 21:10:21 +0000" and return "1".
kr = u.generic.FixupKernel(kr)
kr.KernelVersion = strings.TrimSuffix(kr.KernelVersion, "-Ubuntu")
return kr return kr
} }

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -13,8 +13,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build linux
// Package driverkernel implements the kernel info fetching helpers. // Package driverkernel implements the kernel info fetching helpers.
package driverkernel package driverkernel
@ -40,11 +38,8 @@ func FetchInfo(enforcedKR, enforcedKV string) (kernelrelease.KernelRelease, erro
kr = string(bytes.Trim(u.Release[:], "\x00")) kr = string(bytes.Trim(u.Release[:], "\x00"))
kv = string(bytes.Trim(u.Version[:], "\x00")) kv = string(bytes.Trim(u.Version[:], "\x00"))
} } else {
if enforcedKR != "" {
kr = enforcedKR kr = enforcedKR
}
if enforcedKV != "" {
kv = enforcedKV kv = enforcedKV
} }
kernelRel := kernelrelease.FromString(kr) kernelRel := kernelrelease.FromString(kr)

View File

@ -1,29 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !linux
package driverkernel
import (
"errors"
"github.com/falcosecurity/driverkit/pkg/kernelrelease"
)
// FetchInfo returns information about currently running kernel.
func FetchInfo(_, _ string) (kernelrelease.KernelRelease, error) {
return kernelrelease.KernelRelease{}, errors.New("unsupported")
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -18,15 +18,21 @@ package drivertype
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/homedir"
"github.com/falcosecurity/driverkit/cmd"
"github.com/falcosecurity/driverkit/pkg/kernelrelease" "github.com/falcosecurity/driverkit/pkg/kernelrelease"
"golang.org/x/net/context"
"k8s.io/utils/mount"
"github.com/falcosecurity/falcoctl/internal/utils"
"github.com/falcosecurity/falcoctl/pkg/output" "github.com/falcosecurity/falcoctl/pkg/output"
) )
// TypeBpf is the string for the bpf driver type.
const TypeBpf = "ebpf"
func init() { func init() {
driverTypes[TypeBpf] = &bpf{} driverTypes[TypeBpf] = &bpf{}
} }
@ -68,12 +74,44 @@ func (b *bpf) HasArtifacts() bool {
} }
//nolint:gocritic // the method shall not be able to modify kr //nolint:gocritic // the method shall not be able to modify kr
func (b *bpf) Supported(kr kernelrelease.KernelRelease) bool { func (b *bpf) Build(ctx context.Context,
return kr.SupportsProbe() printer *output.Printer,
_ kernelrelease.KernelRelease,
driverName, driverVersion string,
env map[string]string,
) (string, error) {
// We don't fail if this fails; let's try to build a probe anyway.
_ = mountKernelDebug(printer)
srcPath := fmt.Sprintf("/usr/src/%s-%s/bpf", driverName, driverVersion)
makeCmdArgs := fmt.Sprintf(`make -C %q`, filepath.Clean(srcPath))
makeCmd := exec.CommandContext(ctx, "bash", "-c", makeCmdArgs) //nolint:gosec // false positive
// Append requested env variables to the command env
for key, val := range env {
makeCmd.Env = append(makeCmd.Env, fmt.Sprintf("%s=%s", key, val))
}
out, err := makeCmd.CombinedOutput()
if err != nil {
printer.DefaultText.Print(string(out))
}
outProbe := fmt.Sprintf("%s/probe.o", srcPath)
return outProbe, err
} }
func (b *bpf) ToOutput(destPath string) cmd.OutputOptions { func mountKernelDebug(printer *output.Printer) error {
return cmd.OutputOptions{ // Mount /sys/kernel/debug that is needed on old (pre 4.17) kernel releases,
Probe: destPath, // since these releases still did not support raw tracepoints.
// BPF_PROG_TYPE_RAW_TRACEPOINT was introduced in 4.17 indeed:
// https://github.com/torvalds/linux/commit/c4f6699dfcb8558d138fe838f741b2c10f416cf9
exists, _ := utils.FileExists("/sys/kernel/debug/tracing")
if exists {
return nil
} }
printer.Logger.Info("Mounting debugfs for bpf driver.")
mounter := mount.New("/bin/mount")
err := mounter.Mount("debugfs", "/sys/kernel/debug", "debugfs", []string{"nodev"})
if err != nil {
printer.Logger.Warn("Failed to mount debugfs.", printer.Logger.Args("err", err))
}
return err
} }

View File

@ -1,26 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2025 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package drivertype
const (
// TypeKmod is the string for the kernel module driver type.
TypeKmod = "kmod"
// TypeModernBpf is the string for the modern bpf driver type.
TypeModernBpf = "modern_ebpf"
// TypeBpf is the string for the bpf driver type.
TypeBpf = "ebpf"
)

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -19,21 +19,33 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
"time" "time"
"github.com/falcosecurity/driverkit/cmd"
"github.com/falcosecurity/driverkit/pkg/kernelrelease" "github.com/falcosecurity/driverkit/pkg/kernelrelease"
"golang.org/x/net/context"
"github.com/falcosecurity/falcoctl/pkg/output" "github.com/falcosecurity/falcoctl/pkg/output"
) )
const ( const (
// TypeKmod is the string for the bpf driver type.
TypeKmod = "kmod"
maxRmmodWait = 10 maxRmmodWait = 10
rmmodWaitTime = 5 * time.Second rmmodWaitTime = 5 * time.Second
) )
type errMissingDep struct {
program string
}
func (e *errMissingDep) Error() string {
return fmt.Sprintf("This program requires %s.", e.program)
}
func init() { func init() {
driverTypes[TypeKmod] = &kmod{} driverTypes[TypeKmod] = &kmod{}
} }
@ -49,18 +61,18 @@ func (k *kmod) String() string {
// Then, using dkms, it tries to fetch all // Then, using dkms, it tries to fetch all
// dkms-installed versions of the module to clean them up. // dkms-installed versions of the module to clean them up.
func (k *kmod) Cleanup(printer *output.Printer, driverName string) error { func (k *kmod) Cleanup(printer *output.Printer, driverName string) error {
lsmod, err := exec.LookPath("lsmod") _, err := exec.Command("bash", "-c", "hash lsmod").Output()
if err != nil { if err != nil {
return err return &errMissingDep{program: "lsmod"}
} }
rmmod, err := exec.LookPath("rmmod") _, err = exec.Command("bash", "-c", "hash rmmod").Output()
if err != nil { if err != nil {
return err return &errMissingDep{program: "rmmod"}
} }
kmodName := strings.ReplaceAll(driverName, "-", "_") kmodName := strings.ReplaceAll(driverName, "-", "_")
printer.Logger.Info("Check if kernel module is still loaded.") printer.Logger.Info("Check if kernel module is still loaded.")
lsmodCmdArgs := fmt.Sprintf(`%s | cut -d' ' -f1 | grep -qx %q`, lsmod, kmodName) lsmodCmdArgs := fmt.Sprintf(`lsmod | cut -d' ' -f1 | grep -qx %q`, kmodName)
_, err = exec.Command("bash", "-c", lsmodCmdArgs).Output() //nolint:gosec // false positive _, err = exec.Command("bash", "-c", lsmodCmdArgs).Output() //nolint:gosec // false positive
if err == nil { if err == nil {
unloaded := false unloaded := false
@ -68,7 +80,7 @@ func (k *kmod) Cleanup(printer *output.Printer, driverName string) error {
for i := 0; i < maxRmmodWait; i++ { for i := 0; i < maxRmmodWait; i++ {
printer.Logger.Info("Kernel module is still loaded.") printer.Logger.Info("Kernel module is still loaded.")
printer.Logger.Info("Trying to unload it with 'rmmod'.") printer.Logger.Info("Trying to unload it with 'rmmod'.")
if _, err = exec.Command(rmmod, kmodName).Output(); err == nil { //nolint:gosec // false positive if _, err = exec.Command("rmmod", kmodName).Output(); err == nil { //nolint:gosec // false positive
printer.Logger.Info("OK! Unloading module succeeded.") printer.Logger.Info("OK! Unloading module succeeded.")
unloaded = true unloaded = true
break break
@ -85,14 +97,14 @@ func (k *kmod) Cleanup(printer *output.Printer, driverName string) error {
printer.Logger.Info("OK! There is no module loaded.") printer.Logger.Info("OK! There is no module loaded.")
} }
dkms, err := exec.LookPath("dkms") _, err = exec.Command("bash", "-c", "hash dkms").Output()
if err != nil { if err != nil {
printer.Logger.Info("Skipping dkms remove (dkms not found).") printer.Logger.Info("Skipping dkms remove (dkms not found).")
return nil return nil
} }
printer.Logger.Info("Check all versions of kernel module in dkms.") printer.Logger.Info("Check all versions of kernel module in dkms.")
dkmsLsCmdArgs := fmt.Sprintf(`%s status -m %q | tr -d "," | tr -d ":" | tr "/" " " | cut -d' ' -f2`, dkms, kmodName) dkmsLsCmdArgs := fmt.Sprintf(`dkms status -m %q | tr -d "," | tr -d ":" | tr "/" " " | cut -d' ' -f2`, kmodName)
out, err := exec.Command("bash", "-c", dkmsLsCmdArgs).Output() //nolint:gosec // false positive out, err := exec.Command("bash", "-c", dkmsLsCmdArgs).Output() //nolint:gosec // false positive
if err != nil { if err != nil {
printer.Logger.Warn("Listing kernel module versions failed.", printer.Logger.Args("reason", err)) printer.Logger.Warn("Listing kernel module versions failed.", printer.Logger.Args("reason", err))
@ -106,7 +118,7 @@ func (k *kmod) Cleanup(printer *output.Printer, driverName string) error {
scanner := bufio.NewScanner(outBuffer) scanner := bufio.NewScanner(outBuffer)
for scanner.Scan() { for scanner.Scan() {
dVer := scanner.Text() dVer := scanner.Text()
dkmsRmCmdArgs := fmt.Sprintf(`%s remove -m %s -v %q --all`, dkms, kmodName, dVer) dkmsRmCmdArgs := fmt.Sprintf(`dkms remove -m %s -v %q --all`, kmodName, dVer)
_, err = exec.Command("bash", "-c", dkmsRmCmdArgs).Output() //nolint:gosec // false positive _, err = exec.Command("bash", "-c", dkmsRmCmdArgs).Output() //nolint:gosec // false positive
if err == nil { if err == nil {
printer.Logger.Info("OK! Removing succeeded.", printer.Logger.Args("version", dVer)) printer.Logger.Info("OK! Removing succeeded.", printer.Logger.Args("version", dVer))
@ -153,13 +165,90 @@ func (k *kmod) HasArtifacts() bool {
return true return true
} }
//nolint:gocritic // the method shall not be able to modify kr func createDKMSMakeFile(gcc string) error {
func (k *kmod) Supported(kr kernelrelease.KernelRelease) bool { file, err := os.OpenFile("/tmp/falco-dkms-make", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o777) //nolint:gosec // we need the file to be executable
return kr.SupportsModule() if err != nil {
return err
}
defer file.Close()
_, err = fmt.Fprintln(file, "#!/usr/bin/env bash")
if err == nil {
_, err = fmt.Fprintln(file, `make CC=`+gcc+` $@`)
}
return err
} }
func (k *kmod) ToOutput(destPath string) cmd.OutputOptions { //nolint:gocritic // the method shall not be able to modify kr
return cmd.OutputOptions{ func (k *kmod) Build(ctx context.Context,
Module: destPath, printer *output.Printer,
kr kernelrelease.KernelRelease,
driverName, driverVersion string,
_ map[string]string,
) (string, error) {
// Skip dkms on UEK hosts because it will always fail
if strings.Contains(kr.String(), "uek") {
printer.Logger.Warn("Skipping because the dkms install always fail (on UEK hosts).")
return "", fmt.Errorf("unsupported on uek hosts")
}
out, err := exec.Command("which", "gcc").Output()
if err != nil {
return "", err
}
gccDir := filepath.Dir(string(out))
gccs, err := filepath.Glob(gccDir + "/gcc*")
if err != nil {
return "", err
}
for _, gcc := range gccs {
// Filter away gcc-{ar,nm,...}
// Only gcc compiler has `-print-search-dirs` option.
gccSearchArgs := fmt.Sprintf(`%s -print-search-dirs 2>&1 | grep "install:"`, gcc)
_, err = exec.Command("bash", "-c", gccSearchArgs).Output() //nolint:gosec // false positive
if err != nil {
continue
}
printer.Logger.Info("Trying to dkms install module.", printer.Logger.Args("gcc", gcc))
err = createDKMSMakeFile(gcc)
if err != nil {
printer.Logger.Warn("Could not fill /tmp/falco-dkms-make content.")
continue
}
dkmsCmdArgs := fmt.Sprintf(`dkms install --directive="MAKE='/tmp/falco-dkms-make'" -m %q -v %q -k %q --verbose`,
driverName, driverVersion, kr.String())
// Try the build through dkms
out, err = exec.CommandContext(ctx, "bash", "-c", dkmsCmdArgs).CombinedOutput() //nolint:gosec // false positive
if err == nil {
koGlob := fmt.Sprintf("/var/lib/dkms/%s/%s/%s/%s/module/%s", driverName, driverVersion, kr.String(), kr.Architecture.ToNonDeb(), driverName)
var koFiles []string
koFiles, err = filepath.Glob(koGlob + ".*")
if err != nil || len(koFiles) == 0 {
printer.Logger.Warn("Module file not found.")
continue
}
koFile := koFiles[0]
printer.Logger.Info("Module installed in dkms.", printer.Logger.Args("file", koFile))
return koFile, nil
}
printer.DefaultText.Print(string(out))
dkmsLogFile := fmt.Sprintf("/var/lib/dkms/$%s/%s/build/make.log", driverName, driverVersion)
logs, err := os.ReadFile(filepath.Clean(dkmsLogFile))
if err != nil {
printer.Logger.Warn("Running dkms build failed, couldn't find dkms log", printer.Logger.Args("file", dkmsLogFile))
} else {
printer.Logger.Warn("Running dkms build failed. Dumping dkms log.", printer.Logger.Args("file", dkmsLogFile))
logBuf := bytes.NewBuffer(logs)
scanner := bufio.NewScanner(logBuf)
for scanner.Scan() {
m := scanner.Text()
printer.DefaultText.Println(m)
} }
} }
}
return "", fmt.Errorf("failed to compile the module")
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -13,21 +13,18 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build linux
package drivertype package drivertype
import ( import (
_ "unsafe" // Needed for go:linkname to be able to access a private function from cilium/ebpf/features.
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/features"
"github.com/falcosecurity/driverkit/cmd"
"github.com/falcosecurity/driverkit/pkg/kernelrelease" "github.com/falcosecurity/driverkit/pkg/kernelrelease"
"golang.org/x/net/context"
"github.com/falcosecurity/falcoctl/pkg/output" "github.com/falcosecurity/falcoctl/pkg/output"
) )
// TypeModernBpf is the string for the bpf driver type.
const TypeModernBpf = "modern_ebpf"
func init() { func init() {
driverTypes[TypeModernBpf] = &modernBpf{} driverTypes[TypeModernBpf] = &modernBpf{}
} }
@ -54,31 +51,7 @@ func (m *modernBpf) HasArtifacts() bool {
return false return false
} }
// Get the private symbol `probeProgram` that will be used to test for
// type Tracing, attachType AttachTraceRawTp program availability.
//
//go:linkname probeProgram github.com/cilium/ebpf/features.probeProgram
func probeProgram(spec *ebpf.ProgramSpec) error
//nolint:gocritic // the method shall not be able to modify kr //nolint:gocritic // the method shall not be able to modify kr
func (m *modernBpf) Supported(_ kernelrelease.KernelRelease) bool { func (m *modernBpf) Build(_ context.Context, _ *output.Printer, _ kernelrelease.KernelRelease, _, _ string, _ map[string]string) (string, error) {
// We can't directly use this because it uses the wrong attachtype. return "", nil
// err := features.HaveProgramType(ebpf.Tracing)
// Therefore, we need to manually build a feature test.
// Empty tracing program that just returns 0
progSpec := &ebpf.ProgramSpec{
Type: ebpf.Tracing,
AttachType: ebpf.AttachTraceRawTp,
AttachTo: "sys_enter",
}
err := probeProgram(progSpec)
if err != nil {
return false
}
return features.HaveMapType(ebpf.RingBuf) == nil
}
func (m *modernBpf) ToOutput(_ string) cmd.OutputOptions {
return cmd.OutputOptions{}
} }

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -19,14 +19,14 @@ package drivertype
import ( import (
"fmt" "fmt"
"github.com/falcosecurity/driverkit/cmd"
"github.com/falcosecurity/driverkit/pkg/kernelrelease" "github.com/falcosecurity/driverkit/pkg/kernelrelease"
"golang.org/x/net/context"
"github.com/falcosecurity/falcoctl/pkg/output" "github.com/falcosecurity/falcoctl/pkg/output"
) )
// KernelDirEnv is the env variable set to kernel headers extraction paths. // TypeAuto enables a smart automatic driver selection logic instead of using a fixed driver type.
const KernelDirEnv = "KERNELDIR" const TypeAuto = "auto"
var driverTypes = map[string]DriverType{} var driverTypes = map[string]DriverType{}
@ -37,8 +37,8 @@ type DriverType interface {
Load(printer *output.Printer, src, driverName string, fallback bool) error Load(printer *output.Printer, src, driverName string, fallback bool) error
Extension() string Extension() string
HasArtifacts() bool HasArtifacts() bool
ToOutput(destPath string) cmd.OutputOptions Build(ctx context.Context, printer *output.Printer, kr kernelrelease.KernelRelease,
Supported(kr kernelrelease.KernelRelease) bool driverName, driverVersion string, env map[string]string) (string, error)
} }
// GetTypes return the list of supported driver types. // GetTypes return the list of supported driver types.
@ -47,6 +47,9 @@ func GetTypes() []string {
for key := range driverTypes { for key := range driverTypes {
driverTypesSlice = append(driverTypesSlice, key) driverTypesSlice = append(driverTypesSlice, key)
} }
// auto is a sentinel value to enable automatic driver selection logic,
// but it is not mapped to any DriverType
driverTypesSlice = append(driverTypesSlice, TypeAuto)
return driverTypesSlice return driverTypesSlice
} }
@ -55,5 +58,5 @@ func Parse(driverType string) (DriverType, error) {
if dType, ok := driverTypes[driverType]; ok { if dType, ok := driverTypes[driverType]; ok {
return dType, nil return dType, nil
} }
return nil, fmt.Errorf("unsupported driver type specified: %s", driverType) return nil, fmt.Errorf("wrong driver type specified: %s", driverType)
} }

View File

@ -1,17 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package enum implements the generic logic to manage enum values for Cobra.
package enum

View File

@ -22,10 +22,8 @@ import (
"strings" "strings"
"github.com/falcosecurity/falcoctl/pkg/index/config" "github.com/falcosecurity/falcoctl/pkg/index/config"
"github.com/falcosecurity/falcoctl/pkg/index/fetch/file"
"github.com/falcosecurity/falcoctl/pkg/index/fetch/gcs" "github.com/falcosecurity/falcoctl/pkg/index/fetch/gcs"
"github.com/falcosecurity/falcoctl/pkg/index/fetch/http" "github.com/falcosecurity/falcoctl/pkg/index/fetch/http"
"github.com/falcosecurity/falcoctl/pkg/index/fetch/s3"
"github.com/falcosecurity/falcoctl/pkg/index/index" "github.com/falcosecurity/falcoctl/pkg/index/index"
) )
@ -48,15 +46,11 @@ func NewFetcher() *Fetcher {
"http": http.Fetch, "http": http.Fetch,
"https": http.Fetch, "https": http.Fetch,
"gcs": gcs.Fetch, "gcs": gcs.Fetch,
"file": file.Fetch,
"s3": s3.Fetch,
}, },
schemeDefaultBackends: map[string]string{ schemeDefaultBackends: map[string]string{
"http": "http", "http": "http",
"https": "https", "https": "https",
"gs": "gcs", "gs": "gcs",
"file": "file",
"s3": "s3",
}, },
} }
} }

View File

@ -1,17 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package file implements all the logic for fetching indexes from the local file system.
package file

View File

@ -1,41 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package file
import (
"context"
"fmt"
pkgurl "net/url"
"os"
"github.com/falcosecurity/falcoctl/pkg/index/config"
)
// Fetch fetches the raw index file from the local file system.
func Fetch(_ context.Context, conf *config.Entry) ([]byte, error) {
// Expect URL to be file:///some/directory/filename.yaml
url, err := pkgurl.Parse(conf.URL)
if err != nil {
return nil, fmt.Errorf("cannot parse URL: %w", err)
}
data, err := os.ReadFile(url.Path)
if err != nil {
return nil, fmt.Errorf("reading file: %w", err)
}
return data, nil
}

View File

@ -1,96 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package file
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"github.com/falcosecurity/falcoctl/pkg/index/config"
"github.com/falcosecurity/falcoctl/pkg/index/index"
)
func TestFileFetchWithValidFile(t *testing.T) {
filename := "TestFileFetchWithValidFile-filename.yaml"
entries := []index.Entry{{
Name: "test",
Type: "rulesfile",
Registry: "test.io",
Repository: "test",
Maintainers: index.Maintainer{
{
Email: "test@local",
Name: "test",
},
},
Sources: []string{"/test"},
Keywords: []string{"test"},
}}
ctx := context.Background()
configDir := t.TempDir()
configFile := filepath.Join(configDir, filename)
entryBytes, err := yaml.Marshal(entries)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
err = os.WriteFile(configFile, entryBytes, os.FileMode(0o644))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
b, err := Fetch(ctx, &config.Entry{
Name: "test",
URL: fmt.Sprintf("file://%s/%s", configDir, filename),
Backend: "GCS",
})
assert.NoError(t, err, "error should not occur")
assert.NotNil(t, b, "returned bytes should not be nil")
var resultEntries []index.Entry
err = yaml.Unmarshal(b, &resultEntries)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
assert.Equal(t, entries, resultEntries)
}
func TestFileFetchWithNonExistentFile(t *testing.T) {
filename := "TestFileFetchWithNonExistentFile-filename.yaml"
ctx := context.Background()
configDir := t.TempDir()
// We intentionally do not write out the file here
_, err := Fetch(ctx, &config.Entry{
Name: "test",
URL: fmt.Sprintf("file://%s/%s", configDir, filename),
Backend: "GCS",
})
expectedError := fmt.Sprintf("reading file: open %s/%s: no such file or directory", configDir, filename)
assert.EqualError(t, err, expectedError)
}

View File

@ -1,17 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package s3 implements all the logic for fetching indexes from AWS S3.
package s3

View File

@ -1,63 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package s3
import (
"context"
"fmt"
"io"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
indexConfig "github.com/falcosecurity/falcoctl/pkg/index/config"
)
// Fetch fetches the raw index file from an S3 object.
func Fetch(ctx context.Context, conf *indexConfig.Entry) ([]byte, error) {
o, err := s3ObjectFromURI(conf.URL)
if err != nil {
return nil, err
}
// Create a new AWS config
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
// handle error
return nil, fmt.Errorf("unable to create AWS config: %w", err)
}
svc := s3.NewFromConfig(cfg)
// Get the object from S3
res, err := svc.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(o.Bucket),
Key: aws.String(o.Key),
})
if err != nil {
return nil, fmt.Errorf("unable to get S3 object: %w", err)
}
defer res.Body.Close()
// Read the object data
bytes, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf("error reading S3 object: %w", err)
}
return bytes, nil
}

View File

@ -1,55 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package s3
import (
"fmt"
"net/url"
"strings"
)
const s3Scheme = "s3"
// s3Object represents an S3 object with its bucket and key.
type s3Object struct {
Bucket string
Key string
}
// s3ObjectFromURI parses S3 URIs (s3://<bucket>/<object>) and returns a s3Object.
func s3ObjectFromURI(uri string) (*s3Object, error) {
parsedURI, err := url.Parse(uri)
if err != nil {
return nil, fmt.Errorf("unable to parse URI: %w", err)
}
if !strings.EqualFold(parsedURI.Scheme, s3Scheme) {
return nil, fmt.Errorf("invalid S3 URI: scheme should be '%s' but got '%s'", s3Scheme, parsedURI.Scheme)
}
if parsedURI.Host == "" {
return nil, fmt.Errorf("invalid S3 URI: missing bucket name")
}
if parsedURI.Path == "" {
return nil, fmt.Errorf("invalid S3 URI: missing object name")
}
return &s3Object{
Bucket: parsedURI.Host,
Key: parsedURI.Path[1:], // Remove the leading slash
}, nil
}

View File

@ -63,8 +63,6 @@ type CosignSignature struct {
CertificateIdentity string `yaml:"certificate-identity"` CertificateIdentity string `yaml:"certificate-identity"`
CertificateIdentityRegexp string `yaml:"certificate-identity-regexp"` CertificateIdentityRegexp string `yaml:"certificate-identity-regexp"`
CertificateGithubWorkflow string `yaml:"certificate-github-workflow"` CertificateGithubWorkflow string `yaml:"certificate-github-workflow"`
KeyRef string `yaml:"key"`
IgnoreTlog bool `yaml:"ignore-tlog"`
} }
// Signature represents all the metadata necessary to perform signature verification. // Signature represents all the metadata necessary to perform signature verification.

View File

@ -18,8 +18,8 @@ package authn
import ( import (
"context" "context"
credentials "github.com/oras-project/oras-credentials-go"
"oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/credentials"
"github.com/falcosecurity/falcoctl/internal/login" "github.com/falcosecurity/falcoctl/internal/login"
) )

View File

@ -21,8 +21,8 @@ import (
"net/http" "net/http"
"time" "time"
credentials "github.com/oras-project/oras-credentials-go"
"oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/credentials"
) )
const ( const (

View File

@ -55,7 +55,7 @@ func GCPCredential(ctx context.Context, reg string) (auth.Credential, error) {
// load saved tokenSource or saves it // load saved tokenSource or saves it
if SavedTokenSource == nil { if SavedTokenSource == nil {
tokenSource, err = google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/cloud-platform") tokenSource, err = google.DefaultTokenSource(ctx)
if err != nil { if err != nil {
return auth.EmptyCredential, fmt.Errorf("error while trying to identify a GCP TokenSource %w", err) return auth.EmptyCredential, fmt.Errorf("error while trying to identify a GCP TokenSource %w", err)
} }

View File

@ -203,8 +203,7 @@ func (p *Pusher) Push(ctx context.Context, artifactType oci.ArtifactType,
} }
return &oci.RegistryResult{ return &oci.RegistryResult{
RootDigest: string(rootDesc.Digest), Digest: string(rootDesc.Digest),
Type: artifactType,
}, nil }, nil
} }

View File

@ -138,7 +138,7 @@ var _ = Describe("Pusher", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
// Being the artifact of type plugin we expect that the retrieved descriptor is of type image index. // Being the artifact of type plugin we expect that the retrieved descriptor is of type image index.
Expect(d.MediaType).To(Equal(v1.MediaTypeImageIndex)) Expect(d.MediaType).To(Equal(v1.MediaTypeImageIndex))
Expect(d.Digest.String()).To(Equal(result.RootDigest)) Expect(d.Digest.String()).To(Equal(result.Digest))
Expect(index.Manifests).To(HaveLen(1)) Expect(index.Manifests).To(HaveLen(1))
Expect(fmt.Sprintf("%s/%s", index.Manifests[0].Platform.OS, index.Manifests[0].Platform.Architecture)).To(Equal(testPluginPlatform1)) Expect(fmt.Sprintf("%s/%s", index.Manifests[0].Platform.OS, index.Manifests[0].Platform.Architecture)).To(Equal(testPluginPlatform1))
// Check layer and config media types. // Check layer and config media types.
@ -188,7 +188,7 @@ var _ = Describe("Pusher", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
// Being the artifact of type plugin we expect that the retrieved descriptor is of type image index. // Being the artifact of type plugin we expect that the retrieved descriptor is of type image index.
Expect(d.MediaType).To(Equal(v1.MediaTypeImageIndex)) Expect(d.MediaType).To(Equal(v1.MediaTypeImageIndex))
Expect(d.Digest.String()).To(Equal(result.RootDigest)) Expect(d.Digest.String()).To(Equal(result.Digest))
Expect(index.Manifests).To(HaveLen(3)) Expect(index.Manifests).To(HaveLen(3))
Expect(fmt.Sprintf("%s/%s", index.Manifests[0].Platform.OS, index.Manifests[0].Platform.Architecture)).To(Equal(testPluginPlatform1)) Expect(fmt.Sprintf("%s/%s", index.Manifests[0].Platform.OS, index.Manifests[0].Platform.Architecture)).To(Equal(testPluginPlatform1))
Expect(fmt.Sprintf("%s/%s", index.Manifests[1].Platform.OS, index.Manifests[1].Platform.Architecture)).To(Equal(testPluginPlatform2)) Expect(fmt.Sprintf("%s/%s", index.Manifests[1].Platform.OS, index.Manifests[1].Platform.Architecture)).To(Equal(testPluginPlatform2))
@ -225,7 +225,7 @@ var _ = Describe("Pusher", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
// Being the artifact of type rulesfile we expect that the retrieved descriptor is of type manifest. // Being the artifact of type rulesfile we expect that the retrieved descriptor is of type manifest.
Expect(d.MediaType).To(Equal(v1.MediaTypeImageManifest)) Expect(d.MediaType).To(Equal(v1.MediaTypeImageManifest))
Expect(d.Digest.String()).To(Equal(result.RootDigest)) Expect(d.Digest.String()).To(Equal(result.Digest))
// It must have only one layer since no config layer is configured. // It must have only one layer since no config layer is configured.
Expect(manifest.Layers).To(HaveLen(1)) Expect(manifest.Layers).To(HaveLen(1))
// It must have the rulesfile's layer mediatype. // It must have the rulesfile's layer mediatype.
@ -276,7 +276,7 @@ var _ = Describe("Pusher", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
// Being the artifact of type rulesfile we expect that the retrieved descriptor is of type manifest. // Being the artifact of type rulesfile we expect that the retrieved descriptor is of type manifest.
Expect(d.MediaType).To(Equal(v1.MediaTypeImageManifest)) Expect(d.MediaType).To(Equal(v1.MediaTypeImageManifest))
Expect(d.Digest.String()).To(Equal(result.RootDigest)) Expect(d.Digest.String()).To(Equal(result.Digest))
// It must have only one layer since no config layer is configured. // It must have only one layer since no config layer is configured.
Expect(manifest.Layers).To(HaveLen(1)) Expect(manifest.Layers).To(HaveLen(1))
// It must have the rulesfile's layer mediatype. // It must have the rulesfile's layer mediatype.
@ -348,7 +348,7 @@ var _ = Describe("Pusher", func() {
// Being the artifact of type asset we expect that the retrieved descriptor is of type manifest. // Being the artifact of type asset we expect that the retrieved descriptor is of type manifest.
Expect(d.MediaType).To(Equal(v1.MediaTypeImageManifest)) Expect(d.MediaType).To(Equal(v1.MediaTypeImageManifest))
// Checking the digest is correct. // Checking the digest is correct.
Expect(d.Digest.String()).To(Equal(result.RootDigest)) Expect(d.Digest.String()).To(Equal(result.Digest))
// It must have only one layer since no config layer is configured. // It must have only one layer since no config layer is configured.
Expect(manifest.Layers).To(HaveLen(1)) Expect(manifest.Layers).To(HaveLen(1))
// The layer has to be of type asset. // The layer has to be of type asset.

View File

@ -64,7 +64,7 @@ func WithPlainHTTP(plainHTTP bool) func(r *Repository) {
func (r *Repository) Tags(ctx context.Context) ([]string, error) { func (r *Repository) Tags(ctx context.Context) ([]string, error) {
var result []string var result []string
var tagRetriever = func(tags []string) error { var tagRetriever = func(tags []string) error {
result = append(result, tags...) result = tags
return nil return nil
} }

View File

@ -88,8 +88,8 @@ func HumanReadableMediaType(s string) string {
return string(Asset) return string(Asset)
} }
// If we do not have a match for a well known mediaType then we return the original mediaType. // should never happen
return s return ""
} }
// ArtifactTypeSlice is a slice of ArtifactType, can be passed as comma separated values. // ArtifactTypeSlice is a slice of ArtifactType, can be passed as comma separated values.

View File

@ -19,9 +19,9 @@ import (
"context" "context"
"fmt" "fmt"
credentials "github.com/oras-project/oras-credentials-go"
"oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/credentials"
"github.com/falcosecurity/falcoctl/internal/config" "github.com/falcosecurity/falcoctl/internal/config"
"github.com/falcosecurity/falcoctl/pkg/oci/authn" "github.com/falcosecurity/falcoctl/pkg/oci/authn"

View File

@ -35,7 +35,6 @@ type Artifact struct {
Dependencies []string Dependencies []string
Requirements []string Requirements []string
Tags []string Tags []string
AutoFloatingTags bool
AnnotationSource string AnnotationSource string
} }
@ -65,9 +64,6 @@ func (art *Artifact) AddFlags(cmd *cobra.Command) error {
cmd.Flags().StringArrayVarP(&art.Tags, "tag", "t", nil, cmd.Flags().StringArrayVarP(&art.Tags, "tag", "t", nil,
"additional artifact tag. Can be repeated multiple times") "additional artifact tag. Can be repeated multiple times")
cmd.Flags().BoolVar(&art.AutoFloatingTags, "add-floating-tags", false,
"add the floating tags for the major and minor versions")
cmd.Flags().Var(&art.ArtifactType, "type", cmd.Flags().Var(&art.ArtifactType, "type",
`type of artifact to be pushed. Allowed values: "rulesfile", "plugin", "asset"`) `type of artifact to be pushed. Allowed values: "rulesfile", "plugin", "asset"`)
if err := cmd.MarkFlagRequired("type"); err != nil { if err := cmd.MarkFlagRequired("type"); err != nil {

View File

@ -46,15 +46,15 @@ type Common struct {
// IndexCache caches the entries for the configured indexes. // IndexCache caches the entries for the configured indexes.
IndexCache *cache.Cache IndexCache *cache.Cache
logLevel *output.LogLevel logLevel *LogLevel
logFormat *output.LogFormat logFormat *LogFormat
} }
// NewOptions returns a new Common struct. // NewOptions returns a new Common struct.
func NewOptions() *Common { func NewOptions() *Common {
return &Common{ return &Common{
logLevel: output.NewLogLevel(), logLevel: NewLogLevel(),
logFormat: output.NewLogFormat(), logFormat: NewLogFormat(),
} }
} }

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors // Copyright (C) 2023 The Falco Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -22,17 +22,13 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"github.com/falcosecurity/driverkit/pkg/kernelrelease"
"github.com/falcosecurity/falcoctl/internal/config" "github.com/falcosecurity/falcoctl/internal/config"
driverdistro "github.com/falcosecurity/falcoctl/pkg/driver/distro"
drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type" drivertype "github.com/falcosecurity/falcoctl/pkg/driver/type"
"github.com/falcosecurity/falcoctl/pkg/enum"
) )
// DriverTypes data structure for driver types. // DriverTypes data structure for driver types.
type DriverTypes struct { type DriverTypes struct {
*enum.Enum *Enum
} }
// NewDriverTypes returns a new Enum configured for the driver types. // NewDriverTypes returns a new Enum configured for the driver types.
@ -40,9 +36,7 @@ func NewDriverTypes() *DriverTypes {
types := drivertype.GetTypes() types := drivertype.GetTypes()
sort.Strings(types) sort.Strings(types)
return &DriverTypes{ return &DriverTypes{
// Default value is not used. Enum: NewEnum(types, drivertype.TypeKmod),
// This enum is only used to print allowed values.
Enum: enum.NewEnum(types, drivertype.TypeModernBpf),
} }
} }
@ -53,14 +47,12 @@ type Driver struct {
Repos []string Repos []string
Version string Version string
HostRoot string HostRoot string
Distro driverdistro.Distro
Kr kernelrelease.KernelRelease
} }
// ToDriverConfig maps a Driver options to Driver config struct. // ToDriverConfig maps a Driver options to Driver config struct.
func (d *Driver) ToDriverConfig() *config.Driver { func (d *Driver) ToDriverConfig() *config.Driver {
return &config.Driver{ return &config.Driver{
Type: []string{d.Type.String()}, Type: d.Type.String(),
Name: d.Name, Name: d.Name,
Repos: d.Repos, Repos: d.Repos,
Version: d.Version, Version: d.Version,

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package enum package options
import ( import (
"fmt" "fmt"
@ -26,21 +26,21 @@ import (
// can have a limited set of values. // can have a limited set of values.
type Enum struct { type Enum struct {
allowed []string allowed []string
Value string value string
} }
// NewEnum returns an enum struct. The firs argument is a set of values allowed for the flag. // NewEnum returns an enum struct. The firs argument is a set of values allowed for the flag.
// The second argument is the default Value of the flag. // The second argument is the default value of the flag.
func NewEnum(allowed []string, d string) *Enum { func NewEnum(allowed []string, d string) *Enum {
return &Enum{ return &Enum{
allowed: allowed, allowed: allowed,
Value: d, value: d,
} }
} }
// String returns the Value. // String returns the value.
func (e *Enum) String() string { func (e *Enum) String() string {
return e.Value return e.value
} }
// Allowed returns the list of allowed values enclosed in parenthesis. // Allowed returns the list of allowed values enclosed in parenthesis.
@ -48,12 +48,12 @@ func (e *Enum) Allowed() string {
return fmt.Sprintf("(%s)", strings.Join(e.allowed, ", ")) return fmt.Sprintf("(%s)", strings.Join(e.allowed, ", "))
} }
// Set the Value for the flag. // Set the value for the flag.
func (e *Enum) Set(p string) error { func (e *Enum) Set(p string) error {
if !slices.Contains(e.allowed, p) { if !slices.Contains(e.allowed, p) {
return fmt.Errorf("invalid argument %q, please provide one of (%s)", p, strings.Join(e.allowed, ", ")) return fmt.Errorf("invalid argument %q, please provide one of (%s)", p, strings.Join(e.allowed, ", "))
} }
e.Value = p e.value = p
return nil return nil
} }

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package enum package options
import ( import (
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
@ -41,7 +41,7 @@ var _ = Describe("Enum", func() {
}) })
It("should set the default values", func() { It("should set the default values", func() {
Expect(enum.Value).Should(Equal(defValue)) Expect(enum.value).Should(Equal(defValue))
}) })
It("should set the allowed values", func() { It("should set the allowed values", func() {
@ -64,9 +64,9 @@ var _ = Describe("Enum", func() {
val = newVal val = newVal
}) })
It("Should set the correct value", func() { It("Should set the correct val", func() {
Expect(err).ShouldNot(HaveOccurred()) Expect(err).ShouldNot(HaveOccurred())
Expect(enum.Value).Should(Equal(newVal)) Expect(enum.value).Should(Equal(newVal))
}) })
}) })

View File

@ -13,12 +13,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package output package options
import ( import (
"github.com/pterm/pterm" "github.com/pterm/pterm"
"github.com/falcosecurity/falcoctl/pkg/enum"
) )
const ( const (
@ -34,13 +32,13 @@ var logFormats = []string{LogFormatColor, LogFormatText, LogFormatJSON}
// LogFormat data structure for log-format flag. // LogFormat data structure for log-format flag.
type LogFormat struct { type LogFormat struct {
*enum.Enum *Enum
} }
// NewLogFormat returns a new Enum configured for the log formats flag. // NewLogFormat returns a new Enum configured for the log formats flag.
func NewLogFormat() *LogFormat { func NewLogFormat() *LogFormat {
return &LogFormat{ return &LogFormat{
Enum: enum.NewEnum(logFormats, LogFormatColor), Enum: NewEnum(logFormats, LogFormatColor),
} }
} }
@ -48,9 +46,8 @@ func NewLogFormat() *LogFormat {
func (lg *LogFormat) ToPtermFormatter() pterm.LogFormatter { func (lg *LogFormat) ToPtermFormatter() pterm.LogFormatter {
var formatter pterm.LogFormatter var formatter pterm.LogFormatter
switch lg.Value { switch lg.value {
case LogFormatColor: case LogFormatColor:
pterm.EnableColor()
formatter = pterm.LogFormatterColorful formatter = pterm.LogFormatterColorful
case LogFormatText: case LogFormatText:
pterm.DisableColor() pterm.DisableColor()

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package output package options
import ( import (
"github.com/gookit/color" "github.com/gookit/color"
@ -33,8 +33,8 @@ var _ = Describe("LogFormat", func() {
Context("NewLogFormat Func", func() { Context("NewLogFormat Func", func() {
It("should return a new logFormatter", func() { It("should return a new logFormatter", func() {
Expect(logFormatter).ShouldNot(BeNil()) Expect(logFormatter).ShouldNot(BeNil())
Expect(logFormatter.Value).Should(Equal(LogFormatColor)) Expect(logFormatter.value).Should(Equal(LogFormatColor))
Expect(logFormatter.Allowed()).Should(Equal("(color, text, json)")) Expect(logFormatter.allowed).Should(Equal(logFormats))
}) })
}) })

View File

@ -13,12 +13,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package output package options
import ( import (
"github.com/pterm/pterm" "github.com/pterm/pterm"
"github.com/falcosecurity/falcoctl/pkg/enum"
) )
const ( const (
@ -36,20 +34,20 @@ var logLevels = []string{LogLevelInfo, LogLevelWarn, LogLevelDebug, LogLevelTrac
// LogLevel data structure for log-level flag. // LogLevel data structure for log-level flag.
type LogLevel struct { type LogLevel struct {
*enum.Enum *Enum
} }
// NewLogLevel returns a new Enum configured for the log level flag. // NewLogLevel returns a new Enum configured for the log level flag.
func NewLogLevel() *LogLevel { func NewLogLevel() *LogLevel {
return &LogLevel{ return &LogLevel{
Enum: enum.NewEnum(logLevels, LogLevelInfo), Enum: NewEnum(logLevels, LogLevelInfo),
} }
} }
// ToPtermLogLevel converts the current log level to pterm.LogLevel. // ToPtermLogLevel converts the current log level to pterm.LogLevel.
func (ll *LogLevel) ToPtermLogLevel() pterm.LogLevel { func (ll *LogLevel) ToPtermLogLevel() pterm.LogLevel {
var level pterm.LogLevel var level pterm.LogLevel
switch ll.Value { switch ll.value {
case LogLevelInfo: case LogLevelInfo:
level = pterm.LogLevelInfo level = pterm.LogLevelInfo
case LogLevelWarn: case LogLevelWarn:

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package output package options
import ( import (
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
@ -32,8 +32,8 @@ var _ = Describe("LogLevel", func() {
Context("NewLogLevel Func", func() { Context("NewLogLevel Func", func() {
It("should return a new LogLevel", func() { It("should return a new LogLevel", func() {
Expect(logLevel).ShouldNot(BeNil()) Expect(logLevel).ShouldNot(BeNil())
Expect(logLevel.Value).Should(Equal(LogLevelInfo)) Expect(logLevel.value).Should(Equal(LogLevelInfo))
Expect(logLevel.Allowed()).Should(Equal("(info, warn, debug, trace)")) Expect(logLevel.allowed).Should(Equal(logLevels))
}) })
}) })

View File

@ -17,7 +17,7 @@
# #
# All rules files related to plugins should require at least engine version 10 # All rules files related to plugins should require at least engine version 10
- required_engine_version: 0.10.0 - required_engine_version: 10
- required_plugin_versions: - required_plugin_versions:
- name: cloudtrail - name: cloudtrail

View File

@ -1,31 +0,0 @@
- rule: All Cloudtrail Events
desc: Match all cloudtrail events.
condition:
evt.num > 0
output: Some Cloudtrail Event (evtnum=%evt.num info=%evt.plugininfo ts=%evt.time.iso8601 id=%ct.id error=%ct.error)
priority: DEBUG
tags:
- cloud
- aws
source: aws_cloudtrail
enabled: false
- rule: Console Login Through Assume Role
desc: Detect a console login through Assume Role.
condition:
ct.name="ConsoleLogin" and not ct.error exists
and ct.user.identitytype="AssumedRole"
and json.value[/responseElements/ConsoleLogin]="Success"
output:
Detected a console login through Assume Role
(principal=%ct.user.principalid,
assumedRole=%ct.user.arn,
requesting IP=%ct.srcip,
AWS region=%ct.region)
priority: WARNING
tags:
- cloud
- aws
- aws_console
- aws_iam
source: aws_cloudtrail

View File

@ -36,21 +36,3 @@ func CreateEmptyFile(name string) (string, error) {
return configFile, nil return configFile, nil
} }
// WriteToTmpFile writes data to a temporary file in the specified path.
func WriteToTmpFile(data, dirPath string) (string, error) {
tmpFile, err := os.CreateTemp(dirPath, "rulesfiles-test")
if err != nil {
return "", err
}
if _, err = tmpFile.WriteString(data); err != nil {
return "", err
}
// Get path.
info, err := tmpFile.Stat()
if err != nil {
return "", err
}
return filepath.Join(dirPath, info.Name()), tmpFile.Close()
}

View File

@ -43,7 +43,7 @@ import (
"github.com/go-oauth2/oauth2/v4/models" "github.com/go-oauth2/oauth2/v4/models"
"github.com/go-oauth2/oauth2/v4/server" "github.com/go-oauth2/oauth2/v4/server"
"github.com/go-oauth2/oauth2/v4/store" "github.com/go-oauth2/oauth2/v4/store"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt"
) )
// RegistryTLSConfig maintains all certificate informations. // RegistryTLSConfig maintains all certificate informations.