├── .dockerignore ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build-image.yml │ ├── lint.yml │ ├── push-main.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── CHANGELOG.md ├── Dockerfile ├── Dockerfile.dev ├── LICENSE ├── Makefile ├── OWNERS ├── README.md ├── configuration ├── configuration.go └── version.go ├── docs ├── docs.go ├── swagger.json └── swagger.yaml ├── frontend ├── .babelrc ├── .env.example ├── .eslintrc ├── .gitignore ├── .yarnclean ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── img │ │ └── icons │ │ │ ├── android-chrome-192x192.png │ │ │ └── android-chrome-512x512.png │ ├── index.html │ ├── manifest.json │ └── v.png ├── src │ ├── App.vue │ ├── components │ │ ├── charts │ │ │ ├── bar.vue │ │ │ ├── pie.vue │ │ │ └── timeline.vue │ │ ├── counters.vue │ │ └── filters.vue │ ├── css │ │ └── main.css │ ├── http.js │ ├── main.js │ ├── router │ │ └── index.js │ ├── store │ │ └── index.js │ ├── utils.js │ └── views │ │ ├── .eslintrc.js │ │ ├── DashboardPage.vue │ │ ├── EventsPage.vue │ │ ├── InfoPage.vue │ │ ├── LoginPage.vue │ │ └── TestPage.vue ├── vue.config.js └── yarn.lock ├── go.mod ├── go.sum ├── imgs ├── webui_01.png ├── webui_02.png ├── webui_03.png ├── webui_04.png └── webui_05.png ├── internal ├── api │ └── api.go ├── database │ └── redis │ │ ├── client.go │ │ ├── count.go │ │ ├── index.go │ │ ├── query.go │ │ ├── search.go │ │ └── set.go ├── events │ ├── add.go │ ├── count.go │ └── search.go ├── models │ ├── models.go │ └── outputs.go └── utils │ └── utils.go ├── main.go ├── release └── ldflags.sh └── tools └── go_install.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | **What type of PR is this?** 9 | 10 | > Uncomment one (or more) `/kind <>` lines: 11 | 12 | > /kind bug 13 | 14 | > /kind cleanup 15 | 16 | > /kind design 17 | 18 | > /kind documentation 19 | 20 | > /kind failing-test 21 | 22 | > /kind feature 23 | 24 | 27 | 28 | **Any specific area of the project related to this PR?** 29 | 30 | > Uncomment one (or more) `/area <>` lines: 31 | 32 | > /area build 33 | 34 | 35 | > /area tests 36 | 37 | 38 | 41 | 42 | **What this PR does / why we need it**: 43 | 44 | **Which issue(s) this PR fixes**: 45 | 46 | 51 | 52 | Fixes # 53 | 54 | **Special notes for your reviewer**: 55 | 56 | 57 | -------------------------------------------------------------------------------- /.github/workflows/build-image.yml: -------------------------------------------------------------------------------- 1 | name: build-ci-images 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build-image: 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 13 | 14 | - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 15 | with: 16 | go-version: '1.20' 17 | check-latest: true 18 | cache: true 19 | 20 | - name: Set up QEMU 21 | uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 22 | 23 | - name: Set up Docker Buildx 24 | uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 # v2.9.1 25 | 26 | - uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 # v0.14.3 27 | 28 | - uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0 29 | with: 30 | install-only: true 31 | 32 | - name: run goreleaser-snapshot 33 | run: | 34 | make goreleaser-snapshot 35 | docker images 36 | docker run falcosecurity/falcosidekick-ui:latest-amd64 -v 37 | docker run public.ecr.aws/falcosecurity/falcosidekick-ui:latest-amd64 -v 38 | env: 39 | GOPATH: /home/runner/go 40 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | golangci: 14 | name: lint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 18 | - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 19 | with: 20 | go-version: '1.20' 21 | check-latest: true 22 | - name: golangci-lint 23 | uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299 # v3.6.0 24 | with: 25 | version: v1.53 26 | args: --timeout=5m 27 | -------------------------------------------------------------------------------- /.github/workflows/push-main.yml: -------------------------------------------------------------------------------- 1 | name: push-ci-images 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build-push-image: 11 | permissions: 12 | contents: read 13 | id-token: write 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 18 | 19 | - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 20 | with: 21 | go-version: '1.20' 22 | check-latest: true 23 | cache: true 24 | 25 | - name: Set up QEMU 26 | uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 27 | 28 | - name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 # v2.9.1 30 | 31 | - uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 # v3.1.1 32 | 33 | - uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 # v0.14.3 34 | 35 | - uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0 36 | with: 37 | install-only: true 38 | 39 | - name: run goreleaser-snapshot 40 | run: | 41 | make goreleaser-snapshot 42 | docker images 43 | docker run falcosecurity/falcosidekick-ui:latest-amd64 -v 44 | docker run public.ecr.aws/falcosecurity/falcosidekick-ui:latest-amd64 -v 45 | env: 46 | GOPATH: /home/runner/go 47 | 48 | # Push images to DockerHUB 49 | - name: Login to Docker Hub 50 | uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 51 | with: 52 | username: ${{ secrets.DOCKERHUB_USER }} 53 | password: ${{ secrets.DOCKERHUB_SECRET }} 54 | 55 | - name: Push images to Dockerhub 56 | run: | 57 | docker push falcosecurity/falcosidekick-ui:latest-amd64 58 | docker push falcosecurity/falcosidekick-ui:latest-arm64 59 | docker push falcosecurity/falcosidekick-ui:latest-armv7 60 | docker manifest create --amend falcosecurity/falcosidekick-ui:latest falcosecurity/falcosidekick-ui:latest-amd64 \ 61 | falcosecurity/falcosidekick-ui:latest-arm64 falcosecurity/falcosidekick-ui:latest-armv7 62 | docker manifest push --purge falcosecurity/falcosidekick-ui:latest 63 | 64 | # Push images to AWS Public ECR 65 | - name: Configure AWS credentials 66 | uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 67 | with: 68 | role-to-assume: arn:aws:iam::292999226676:role/github_actions-falcosidekick-ui-ecr 69 | aws-region: us-east-1 70 | 71 | - name: Login to Amazon ECR 72 | id: login-ecr-public 73 | uses: aws-actions/amazon-ecr-login@fc3959cb4cf5a821ab7a5a636ea4f1e855b05180 # v1.6.2 74 | with: 75 | registry-type: public 76 | 77 | - run: | 78 | docker push public.ecr.aws/falcosecurity/falcosidekick-ui:latest-amd64 79 | docker push public.ecr.aws/falcosecurity/falcosidekick-ui:latest-arm64 80 | docker push public.ecr.aws/falcosecurity/falcosidekick-ui:latest-armv7 81 | docker manifest create --amend public.ecr.aws/falcosecurity/falcosidekick-ui:latest public.ecr.aws/falcosecurity/falcosidekick-ui:latest-amd64 \ 82 | public.ecr.aws/falcosecurity/falcosidekick-ui:latest-arm64 public.ecr.aws/falcosecurity/falcosidekick-ui:latest-armv7 83 | docker manifest push --purge public.ecr.aws/falcosecurity/falcosidekick-ui:latest 84 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | concurrency: release 9 | 10 | permissions: 11 | contents: write # needed to write releases 12 | id-token: write # needed for keyless signing 13 | 14 | jobs: 15 | release: 16 | outputs: 17 | hashes: ${{ steps.hash.outputs.hashes }} 18 | tag_name: ${{ steps.tag.outputs.tag_name }} 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 22 | 23 | - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 24 | with: 25 | go-version: '1.20' 26 | check-latest: true 27 | 28 | - name: Set up QEMU 29 | uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 30 | 31 | - name: Set up Docker Buildx 32 | uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 # v2.9.1 33 | 34 | - uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 35 | 36 | - uses: anchore/sbom-action/download-syft@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 # v0.14.3 37 | 38 | # Push images to DockerHUB 39 | - name: Login to Docker Hub 40 | uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 41 | with: 42 | username: ${{ secrets.DOCKERHUB_USER }} 43 | password: ${{ secrets.DOCKERHUB_SECRET }} 44 | 45 | # Push images to AWS Public ECR 46 | - name: Configure AWS credentials 47 | uses: aws-actions/configure-aws-credentials@5fd3084fc36e372ff1fff382a39b10d03659f355 # v2.2.0 48 | with: 49 | role-to-assume: arn:aws:iam::292999226676:role/github_actions-falcosidekick-ui-ecr 50 | aws-region: us-east-1 51 | 52 | - name: Login to Amazon ECR 53 | id: login-ecr-public 54 | uses: aws-actions/amazon-ecr-login@fc3959cb4cf5a821ab7a5a636ea4f1e855b05180 # v1.6.2 55 | with: 56 | registry-type: public 57 | 58 | - name: Set LDFLAGS 59 | id: ldflags 60 | run: | 61 | source ./release/ldflags.sh 62 | goflags=$(ldflags) 63 | echo "GO_FLAGS="${goflags}"" >> "$GITHUB_ENV" 64 | 65 | - name: Set tag output 66 | id: tag 67 | run: echo "tag_name=${GITHUB_REF#refs/*/}" >> "$GITHUB_OUTPUT" 68 | 69 | - name: Run GoReleaser 70 | id: run-goreleaser 71 | uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0 72 | with: 73 | version: latest 74 | args: release --clean --timeout 120m 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | LDFLAGS: ${{ env.GO_FLAGS }} 78 | GOPATH: /home/runner/go 79 | 80 | - name: Generate subject 81 | id: hash 82 | env: 83 | ARTIFACTS: "${{ steps.run-goreleaser.outputs.artifacts }}" 84 | run: | 85 | set -euo pipefail 86 | checksum_file=$(echo "$ARTIFACTS" | jq -r '.[] | select (.type=="Checksum") | .path') 87 | echo "hashes=$(cat $checksum_file | base64 -w0)" >> "$GITHUB_OUTPUT" 88 | 89 | provenance: 90 | needs: [release] 91 | permissions: 92 | actions: read # To read the workflow path. 93 | id-token: write # To sign the provenance. 94 | contents: write # To add assets to a release. 95 | uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.8.0 96 | with: 97 | base64-subjects: "${{ needs.release.outputs.hashes }}" 98 | upload-assets: true 99 | upload-tag-name: "${{ needs.release.outputs.tag_name }}" 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | falcosidekick-ui 15 | .env 16 | .vscode 17 | .idea 18 | *.swp 19 | /tools/bin/* 20 | dist/* 21 | 22 | tmp/ 23 | .DS_Store 24 | 25 | dist-goreleaser 26 | 27 | */yarn-error.log 28 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: falcosidekick-ui 2 | 3 | env: 4 | - GO111MODULE=on 5 | - DOCKER_CLI_EXPERIMENTAL=enabled 6 | - BUILDX_PLATFORMS=linux/amd64,linux/arm64,linux/arm/v7 7 | - COSIGN_YES=true 8 | 9 | snapshot: 10 | name_template: 'latest' 11 | 12 | checksum: 13 | name_template: 'checksums.txt' 14 | 15 | before: 16 | hooks: 17 | - go mod tidy 18 | - make frontend 19 | 20 | sboms: 21 | - artifacts: archive 22 | 23 | builds: 24 | - id: "falcosidekick-ui" 25 | goos: 26 | - linux 27 | goarch: 28 | - amd64 29 | - arm64 30 | - arm 31 | goarm: 32 | - '7' 33 | asmflags: 34 | - all=-trimpath={{.Env.GOPATH}} 35 | gcflags: 36 | - all=-trimpath={{.Env.GOPATH}} 37 | main: main.go 38 | flags: 39 | - -trimpath 40 | env: 41 | - CGO_ENABLED=0 42 | ldflags: 43 | - "{{ .Env.LDFLAGS }}" 44 | binary: falcosidekick-ui 45 | 46 | dockers: 47 | - goos: linux 48 | goarch: amd64 49 | dockerfile: Dockerfile 50 | use: buildx 51 | image_templates: 52 | - "falcosecurity/falcosidekick-ui:stable-amd64" 53 | - "falcosecurity/falcosidekick-ui:{{ .Version }}-amd64" 54 | - "public.ecr.aws/falcosecurity/falcosidekick-ui:stable-amd64" 55 | - "public.ecr.aws/falcosecurity/falcosidekick-ui:{{ .Version }}-amd64" 56 | build_flag_templates: 57 | - "--pull" 58 | - "--label=org.opencontainers.image.created={{.Date}}" 59 | - "--label=org.opencontainers.image.name={{.ProjectName}}" 60 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 61 | - "--label=org.opencontainers.image.version={{.Version}}" 62 | - "--platform=linux/amd64" 63 | extra_files: 64 | - frontend/dist 65 | - LICENSE 66 | 67 | - goos: linux 68 | goarch: arm64 69 | dockerfile: Dockerfile 70 | use: buildx 71 | image_templates: 72 | - "falcosecurity/falcosidekick-ui:stable-arm64" 73 | - "falcosecurity/falcosidekick-ui:{{ .Version }}-arm64" 74 | - "public.ecr.aws/falcosecurity/falcosidekick-ui:stable-arm64" 75 | - "public.ecr.aws/falcosecurity/falcosidekick-ui:{{ .Version }}-arm64" 76 | build_flag_templates: 77 | - "--pull" 78 | - "--label=org.opencontainers.image.created={{.Date}}" 79 | - "--label=org.opencontainers.image.name={{.ProjectName}}" 80 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 81 | - "--label=org.opencontainers.image.version={{.Version}}" 82 | - "--platform=linux/arm64" 83 | extra_files: 84 | - frontend/dist 85 | - LICENSE 86 | 87 | - goos: linux 88 | goarch: arm 89 | goarm: '7' 90 | dockerfile: Dockerfile 91 | use: buildx 92 | image_templates: 93 | - "falcosecurity/falcosidekick-ui:stable-armv7" 94 | - "falcosecurity/falcosidekick-ui:{{ .Version }}-armv7" 95 | - "public.ecr.aws/falcosecurity/falcosidekick-ui:stable-armv7" 96 | - "public.ecr.aws/falcosecurity/falcosidekick-ui:{{ .Version }}-armv7" 97 | build_flag_templates: 98 | - "--pull" 99 | - "--label=org.opencontainers.image.created={{.Date}}" 100 | - "--label=org.opencontainers.image.name={{.ProjectName}}" 101 | - "--label=org.opencontainers.image.revision={{.FullCommit}}" 102 | - "--label=org.opencontainers.image.version={{.Version}}" 103 | - "--platform=linux/arm/v7" 104 | extra_files: 105 | - frontend/dist 106 | - LICENSE 107 | 108 | docker_manifests: 109 | - name_template: 'falcosecurity/falcosidekick-ui:stable' 110 | image_templates: 111 | - 'falcosecurity/falcosidekick-ui:stable-amd64' 112 | - 'falcosecurity/falcosidekick-ui:stable-arm64' 113 | - 'falcosecurity/falcosidekick-ui:stable-armv7' 114 | - name_template: 'falcosecurity/falcosidekick-ui:{{ .Version }}' 115 | image_templates: 116 | - 'falcosecurity/falcosidekick-ui:{{ .Version }}-amd64' 117 | - 'falcosecurity/falcosidekick-ui:{{ .Version }}-arm64' 118 | - 'falcosecurity/falcosidekick-ui:{{ .Version }}-armv7' 119 | - name_template: 'public.ecr.aws/falcosecurity/falcosidekick-ui:stable' 120 | image_templates: 121 | - 'public.ecr.aws/falcosecurity/falcosidekick-ui:stable-amd64' 122 | - 'public.ecr.aws/falcosecurity/falcosidekick-ui:stable-arm64' 123 | - 'public.ecr.aws/falcosecurity/falcosidekick-ui:stable-armv7' 124 | - name_template: 'public.ecr.aws/falcosecurity/falcosidekick-ui:{{ .Version }}' 125 | image_templates: 126 | - 'public.ecr.aws/falcosecurity/falcosidekick-ui:{{ .Version }}-amd64' 127 | - 'public.ecr.aws/falcosecurity/falcosidekick-ui:{{ .Version }}-arm64' 128 | - 'public.ecr.aws/falcosecurity/falcosidekick-ui:{{ .Version }}-armv7' 129 | 130 | signs: 131 | - id: falcosidekick-ui 132 | signature: "${artifact}.sig" 133 | certificate: "${artifact}.pem" 134 | cmd: cosign 135 | args: ["sign-blob", "--output-signature", "${artifact}.sig", "--output-certificate", "${artifact}.pem", "${artifact}"] 136 | artifacts: archive 137 | - id: checksum 138 | signature: "${artifact}.sig" 139 | certificate: "${artifact}.pem" 140 | cmd: cosign 141 | args: ["sign-blob", "--output-signature", "${artifact}.sig", "--output-certificate", "${artifact}.pem", "${artifact}"] 142 | artifacts: checksum 143 | 144 | docker_signs: 145 | - id: falcosidekick-ui 146 | cmd: cosign 147 | args: ["sign", "--recursive", "${artifact}"] 148 | artifacts: manifests 149 | output: true 150 | 151 | release: 152 | github: 153 | owner: falcosecurity 154 | name: falcosidekick-ui 155 | prerelease: auto 156 | 157 | dist: dist-goreleaser 158 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v2.2.0 2 | 3 | * Replace CircleCI by Github Actions 4 | * Add new dialog box to show the details of an alert 5 | * Add an export feature 6 | * Allow to specify the TTL for the keys with an unit (second, minute, day, week) 7 | * Allow to set a password for Redis 8 | * Allow to disable the basic auth 9 | * Add a button to clear the search input 10 | * Fix missing filter/search in query parameters 11 | * Fix search for hostnames and tags with dashes 12 | * Fix error with empty result from `count_by` 13 | 14 | # v2.1.0 15 | 16 | * Add Basic Auth 17 | * Update log format + remove the bootstrap banner 18 | * Allow to set Log Level 19 | * Add `Hostname` field as filter 20 | * Refactor `Info` view 21 | * Fix the results of `count_by` 22 | * Add autorefresh 23 | * Allow to set a `TTL` for keys in Redis 24 | * Allow to set the settings with env vars 25 | * Use Host URL as API baseURL 26 | 27 | # v2.0.2 28 | 29 | * Fix force redirect to localhost 30 | 31 | # v2.0.1 32 | 33 | * Fix empty timelines 34 | * Fix loss of query strings when the page is changed 35 | * Change prefix in logs when an event is added 36 | 37 | # v2.0.0 38 | 39 | * Full rewrite (frontend + backend) 40 | * The backend has been rewritten with Echo framework and exposes an API (in RO) to count, search, etc the events. 41 | * For the storage, and allow full text search, a Redis datababse with module [Redisearch](redis.io/docs/stack/search is used) 42 | * The frontend is created with Vuejs 2 + Vuetify 43 | 44 | # v1.1.0 45 | 46 | * Implement `DarkMode` for Falcosidekick UI 47 | 48 | # v1.0.1 49 | 50 | * Fix `Mixed content` error when the UI is reached by https 51 | 52 | # v1.0.0 53 | 54 | * New design 55 | * Dynamic time range 56 | 57 | # v0.2.0 58 | 59 | * Render large output fields as message boxes to prevent horizontal scrolling 60 | * Fix Logo position on larger screens 61 | * Increase the default max number of events in memory to 200 (up from 50) 62 | * Add a frontend based time range filter 63 | 64 | # v0.1.0 65 | 66 | * First release -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=alpine:3.15 2 | 3 | # Final Docker image 4 | FROM ${BASE_IMAGE} AS final-stage 5 | LABEL MAINTAINER "Thomas Labarussias " 6 | RUN apk add --update --no-cache ca-certificates 7 | # Create user falcosidekick 8 | RUN addgroup -S falcosidekickui && adduser -u 1234 -S falcosidekickui -G falcosidekickui 9 | # must be numeric to work with Pod Security Policies: 10 | # https://kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups 11 | USER 1234 12 | WORKDIR /app 13 | COPY frontend/dist frontend/dist 14 | COPY LICENSE . 15 | COPY falcosidekick-ui . 16 | 17 | EXPOSE 2802 18 | ENTRYPOINT ["./falcosidekick-ui"] 19 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | ARG BUILDER_IMAGE=golang:1.19-buster 2 | ARG BASE_IMAGE=alpine:3.15 3 | 4 | FROM ${BUILDER_IMAGE} AS build-stage 5 | 6 | ENV CGO_ENABLED=0 7 | 8 | WORKDIR /src/ 9 | COPY . . 10 | 11 | RUN make falcosidekick-ui-backend-only 12 | 13 | # Final Docker image 14 | FROM ${BASE_IMAGE} AS final-stage 15 | LABEL MAINTAINER "Thomas Labarussias " 16 | 17 | RUN apk add --update --no-cache ca-certificates 18 | 19 | # Create user falcosidekick 20 | RUN addgroup -S falcosidekick && adduser -u 1234 -S falcosidekick -G falcosidekick 21 | # must be numeric to work with Pod Security Policies: 22 | # https://kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups 23 | USER 1234 24 | 25 | WORKDIR ${HOME}/app 26 | COPY frontend/dist frontend/dist 27 | COPY LICENSE . 28 | COPY --from=build-stage /src/falcosidekick-ui . 29 | 30 | EXPOSE 2801 31 | 32 | ENTRYPOINT ["./falcosidekick-ui"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # 3 | # Copyright (C) 2023 The Falco Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 11 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | # specific language governing permissions and limitations under the License. 13 | # 14 | 15 | # Ensure Make is run with bash shell as some syntax below is bash-specific 16 | SHELL=/bin/bash -o pipefail 17 | 18 | .DEFAULT_GOAL:=help 19 | 20 | GOPATH := $(shell go env GOPATH) 21 | GOARCH := $(shell go env GOARCH) 22 | GOOS := $(shell go env GOOS) 23 | GOPROXY := $(shell go env GOPROXY) 24 | ifeq ($(GOPROXY),) 25 | GOPROXY := https://proxy.golang.org 26 | endif 27 | export GOPROXY 28 | GO ?= go 29 | DOCKER ?= docker 30 | TEST_FLAGS ?= -v -race 31 | 32 | # Directories. 33 | TOOLS_DIR := tools 34 | TOOLS_BIN_DIR := $(abspath $(TOOLS_DIR)/bin) 35 | GO_INSTALL = tools/go_install.sh 36 | 37 | # Binaries. 38 | GOLANGCI_LINT_VER := v1.52.2 39 | GOLANGCI_LINT_BIN := golangci-lint 40 | GOLANGCI_LINT := $(TOOLS_BIN_DIR)/$(GOLANGCI_LINT_BIN)-$(GOLANGCI_LINT_VER) 41 | 42 | GIT_TAG ?= dirty-tag 43 | GIT_VERSION ?= $(shell git describe --tags --always --dirty) 44 | GIT_HASH ?= $(shell git rev-parse HEAD) 45 | DATE_FMT = +'%Y-%m-%dT%H:%M:%SZ' 46 | SOURCE_DATE_EPOCH ?= $(shell git log -1 --pretty=%ct) 47 | ifdef SOURCE_DATE_EPOCH 48 | BUILD_DATE ?= $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u "$(DATE_FMT)") 49 | else 50 | BUILD_DATE ?= $(shell date "$(DATE_FMT)") 51 | endif 52 | GIT_TREESTATE = "clean" 53 | DIFF = $(shell git diff --quiet >/dev/null 2>&1; if [ $$? -eq 1 ]; then echo "1"; fi) 54 | ifeq ($(DIFF), 1) 55 | GIT_TREESTATE = "dirty" 56 | endif 57 | 58 | PKG = github.com/falcosecurity/falcosidekick-ui/configuration 59 | LDFLAGS=-X $(PKG).GitVersion=$(GIT_VERSION) -X $(PKG).GitCommit=$(GIT_HASH) -X $(PKG).GitTreeState=$(GIT_TREESTATE) -X $(PKG).BuildDate=$(BUILD_DATE) 60 | 61 | ## -------------------------------------- 62 | ## Dev 63 | ## -------------------------------------- 64 | 65 | .PHONY: server 66 | server: 67 | cd ./frontend && yarn serve 68 | 69 | ## -------------------------------------- 70 | ## Docs 71 | ## -------------------------------------- 72 | 73 | .PHONY: docs 74 | docs: 75 | swag fmt 76 | swag init 77 | 78 | ## -------------------------------------- 79 | ## Build 80 | ## -------------------------------------- 81 | 82 | .PHONY: frontend 83 | frontend: 84 | cd frontend \ 85 | && yarn install \ 86 | && yarn build 87 | 88 | .PHONY: falcosidekick-ui 89 | falcosidekick-ui: frontend 90 | $(GO) mod download 91 | $(GO) build -trimpath -ldflags "$(LDFLAGS)" -o falcosidekick-ui . 92 | 93 | .PHONY: falcosidekick-ui-linux-amd64 94 | falcosidekick-ui-linux-amd64: frontend 95 | $(GO) mod download 96 | GOOS=linux GOARCH=amd64 $(GO) build -gcflags all=-trimpath=/src -asmflags all=-trimpath=/src -a -installsuffix cgo -o falcosidekick-ui . 97 | 98 | .PHONY: falcosidekick-ui-backend-only 99 | falcosidekick-ui-backend-only: 100 | $(GO) mod download 101 | $(GO) build -trimpath -ldflags "$(LDFLAGS)" -o falcosidekick-ui . 102 | 103 | .PHONY: falcosidekick-ui-linux-amd64-backend-only 104 | falcosidekick-ui-linux-amd64: 105 | $(GO) mod download 106 | GOOS=linux GOARCH=amd64 $(GO) build -gcflags all=-trimpath=/src -asmflags all=-trimpath=/src -a -installsuffix cgo -o falcosidekick-ui . 107 | 108 | .PHONY: build-image 109 | build-image: 110 | $(DOCKER) build -t falcosecurity/falcosidekick-ui:latest . --no-cache 111 | 112 | ## -------------------------------------- 113 | ## Test 114 | ## -------------------------------------- 115 | 116 | .PHONY: test 117 | test: 118 | $(GO) vet ./... 119 | $(GO) test ${TEST_FLAGS} ./... 120 | 121 | .PHONY: test-coverage 122 | test-coverage: 123 | $(GO) test ./outputs -count=1 -cover -v ./... 124 | 125 | ## -------------------------------------- 126 | ## Linting 127 | ## -------------------------------------- 128 | 129 | .PHONY: lint 130 | lint: $(GOLANGCI_LINT) ## Lint codebase 131 | $(GOLANGCI_LINT) run -v 132 | 133 | lint-full: $(GOLANGCI_LINT) ## Run slower linters to detect possible issues 134 | $(GOLANGCI_LINT) run -v --fast=false 135 | 136 | ## -------------------------------------- 137 | ## Release 138 | ## -------------------------------------- 139 | 140 | .PHONY: goreleaser 141 | goreleaser: ## Release using goreleaser 142 | LDFLAGS="$(LDFLAGS)" goreleaser release --clean 143 | 144 | .PHONY: goreleaser-snapshot 145 | goreleaser-snapshot: ## Release snapshot using goreleaser 146 | LDFLAGS="$(LDFLAGS)" goreleaser --snapshot --skip=sign --clean 147 | 148 | ## -------------------------------------- 149 | ## Tooling Binaries 150 | ## -------------------------------------- 151 | 152 | $(GOLANGCI_LINT): ## Build golangci-lint from tools folder. 153 | GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) github.com/golangci/golangci-lint/cmd/golangci-lint $(GOLANGCI_LINT_BIN) $(GOLANGCI_LINT_VER) 154 | 155 | ## -------------------------------------- 156 | ## Cleanup / Verification 157 | ## -------------------------------------- 158 | 159 | .PHONY: clean 160 | clean: 161 | rm -rf hack/tools/bin 162 | rm -rf dist -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - Issif 3 | - cpanato 4 | - fjogeleit 5 | emeritus_approvers: 6 | - leogr 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Falcosidekick-ui 2 | 3 | [![Falco Ecosystem Repository](https://github.com/falcosecurity/evolution/blob/main/repos/badges/falco-ecosystem-blue.svg)](https://github.com/falcosecurity/evolution/blob/main/REPOSITORIES.md#ecosystem-scope) [![Incubating](https://img.shields.io/badge/status-incubating-orange?style=for-the-badge)](https://github.com/falcosecurity/evolution/blob/main/REPOSITORIES.md#incubating) 4 | 5 | 6 | ![release](https://flat.badgen.net/github/release/falcosecurity/falcosidekick-ui/latest?color=green) ![last commit](https://flat.badgen.net/github/last-commit/falcosecurity/falcosidekick-ui) ![licence](https://flat.badgen.net/badge/license/Apache/blue) ![docker pulls](https://flat.badgen.net/docker/pulls/falcosecurity/falcosidekick-ui?icon=docker) 7 | 8 | ## Description 9 | 10 | A simple WebUI for displaying latest events from [Falco](https://falco.org). It works as output for [Falcosidekick](https://github.com/falcosecurity/falcosidekick). 11 | 12 | ## Requirements 13 | 14 | Events are stored in a `Redis` server with [`Redisearch`](https://github.com/RediSearch/RediSearch) module (> v2). 15 | 16 | ## Usage 17 | 18 | ### Options 19 | #### Precedence: flag value -> environment variable value -> default value 20 | 21 | ```shell 22 | Usage of Falcosidekick-UI: 23 | -a string 24 | Listen Address (default "0.0.0.0", environment "FALCOSIDEKICK_UI_ADDR") 25 | -d boolean 26 | Disable authentication (environment "FALCOSIDEKICK_UI_DISABLEAUTH") 27 | -l string 28 | Log level: "debug", "info", "warning", "error" (default "info", environment "FALCOSIDEKICK_UI_LOGLEVEL") 29 | -p int 30 | Listen Port (default "2802", environment "FALCOSIDEKICK_UI_PORT") 31 | -r string 32 | Redis server address (default "localhost:6379", environment "FALCOSIDEKICK_UI_REDIS_URL") 33 | -t string 34 | TTL for keys, the format is X, 35 | with unit (s, m, h, d, W, M, y)" (default "0", environment "FALCOSIDEKICK_UI_TTL") 36 | -u string 37 | User in format : (default "admin:admin", environment "FALCOSIDEKICK_UI_USER") 38 | -v boolean 39 | Display version 40 | -w string 41 | Redis password (default "", environment "FALCOSIDEKICK_UI_REDIS_PASSWORD") 42 | -x boolean 43 | Allow CORS for development (environment "FALCOSIDEKICK_UI_DEV") 44 | ``` 45 | 46 | > If not user is set and the authentication is not disabled, the default user is `admin:admin` 47 | 48 | ### Run with docker 49 | 50 | ```shell 51 | docker run -d -p 2802:2802 falcosecurity/falcosidekick-ui 52 | ``` 53 | 54 | ### Run 55 | 56 | ``` 57 | git clone https://github.com/falcosecurity/falcosidekick-ui.git 58 | cd falcosidekick-ui 59 | 60 | go run . 61 | #or 62 | make falcosidekick-ui && ./falcosidekick-ui 63 | ``` 64 | 65 | ### Endpoints 66 | 67 | | Route | Method | Query Parameters | Usage | 68 | | :------ | :----: | :--------------- | :--------------- | 69 | | `/docs` | `GET` | none | Get Swagger Docs | 70 | | `/` | `GET` | none | Display WebUI | 71 | 72 | #### UI 73 | 74 | The UI is reachable by default at `http://localhost:2802/`. 75 | 76 | #### API 77 | 78 | > The prefix for access to the API is `/api/v1/`. 79 | > The base URL for the API is `http://localhost:2802/api/v1/`. 80 | 81 | | Route | Method | Query Parameters | Usage | 82 | | :-------------------------- | :----: | :----------------------------------------------------------------------- | :----------------------------------- | 83 | | `/` | `POST` | none | Add event | 84 | | `/healthz` | `GET` | none | Healthcheck | 85 | | `/authenticate`, `/auth` | `POST` | none | Authenticate | 86 | | `/configuration`, `/config` | `GET` | none | Get Configuration | 87 | | `/outputs` | `GET` | none | Get list of Outputs of Falcosidekick | 88 | | `/event/count` | `GET` | `pretty`, `priority`, `rule`, `filter`, `tags`, `since`, `limit`, `page` | Count all events | 89 | | `/event/count/priority` | `GET` | `pretty`, `priority`, `rule`, `filter`, `tags`, `since`, `limit`, `page` | Count events by priority | 90 | | `/event/count/rule` | `GET` | `pretty`, `priority`, `rule`, `filter`, `tags`, `since`, `limit`, `page` | Count events by rule | 91 | | `/event/count/source` | `GET` | `pretty`, `priority`, `rule`, `filter`, `tags`, `since`, `limit`, `page` | Count events by source | 92 | | `/event/count/tags` | `GET` | `pretty`, `priority`, `rule`, `filter`, `tags`, `since`, `limit`, `page` | Count events by tags | 93 | | `/event/search` | `GET` | `pretty`, `priority`, `rule`, `filter`, `tags`, `since`, `limit`, `page` | Search events | 94 | 95 | All responses are in JSON format. 96 | 97 | Query parameters list: 98 | * `pretty`: return well formated JSON 99 | * `priority`: filter by priority 100 | * `rule`: filter by rule 101 | * `filter`: filter by term 102 | * `source`: filter by source 103 | * `tags`: filter by tags 104 | * `since`: filter by since (in 'second', 'min', 'day', 'week', 'month', 'year') 105 | * `limit`: limit number of results (default: 100) 106 | * `page`: page of results 107 | 108 | ## Development 109 | 110 | ### Start local redis server 111 | 112 | ```shell 113 | docker run -d -p 6379:6379 redislabs/redisearch:2.2.4 114 | ``` 115 | 116 | ### Build 117 | 118 | Requirements: 119 | * `go` >= 1.18 120 | * `nodejs` >= v14 121 | * `yarn` >= 1.22 122 | 123 | ```shell 124 | make falcosidekick-ui 125 | ``` 126 | 127 | ### Lint 128 | 129 | ```shell 130 | make lint 131 | ``` 132 | 133 | ### Full lint 134 | 135 | ```shell 136 | make lint-full 137 | ``` 138 | 139 | ### Update Docs 140 | 141 | Requirement: 142 | * [`swag`](https://github.com/swaggo/swag) 143 | 144 | ```shell 145 | make docs 146 | ``` 147 | 148 | ## Screenshots 149 | 150 | ![falcosidekick-ui](imgs/webui_01.png) 151 | ![falcosidekick-ui](imgs/webui_02.png) 152 | ![falcosidekick-ui](imgs/webui_03.png) 153 | ![falcosidekick-ui](imgs/webui_04.png) 154 | ![falcosidekick-ui](imgs/webui_05.png) 155 | 156 | ## Authors 157 | 158 | * Thomas Labarussias (https://github.com/Issif) 159 | * Frank Jogeleit (https://github.com/fjogeleit) 160 | -------------------------------------------------------------------------------- /configuration/configuration.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package configuration 16 | 17 | type Configuration struct { 18 | // DisplayMode string `json:"display-mode"` 19 | ListenAddress string `json:"listen-address"` 20 | ListenPort int `json:"listen-port"` 21 | RedisServer string `json:"redis-server"` 22 | RedisPassword string `json:"redis-password"` 23 | DevMode bool `json:"dev-mode"` 24 | DisableAuth bool `json:"disable-auth"` 25 | LogLevel string `json:"log-level"` 26 | TTL int `json:"ttl"` 27 | Credentials string `json:"credentials"` 28 | } 29 | 30 | var config *Configuration 31 | 32 | func CreateConfiguration() *Configuration { 33 | config = new(Configuration) 34 | return config 35 | } 36 | 37 | func GetConfiguration() *Configuration { 38 | return config 39 | } 40 | -------------------------------------------------------------------------------- /configuration/version.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package configuration 16 | 17 | import ( 18 | "fmt" 19 | "runtime" 20 | "strings" 21 | "text/tabwriter" 22 | ) 23 | 24 | // Base version information. 25 | // 26 | // This is the fallback data used when version information from git is not 27 | // provided via go ldflags (e.g. via Makefile). 28 | var ( 29 | // Output of "git describe". The prerequisite is that the branch should be 30 | // tagged using the correct versioning strategy. 31 | GitVersion = "devel" 32 | // SHA1 from git, output of $(git rev-parse HEAD) 33 | GitCommit = "unknown" 34 | // State of git tree, either "clean" or "dirty" 35 | GitTreeState = "unknown" 36 | // Build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') 37 | BuildDate = "unknown" 38 | ) 39 | 40 | type VersionInfo struct { 41 | GitVersion string 42 | GitCommit string 43 | GitTreeState string 44 | BuildDate string 45 | GoVersion string 46 | Compiler string 47 | Platform string 48 | } 49 | 50 | func GetVersionInfo() VersionInfo { 51 | return VersionInfo{ 52 | GitVersion: GitVersion, 53 | GitCommit: GitCommit, 54 | GitTreeState: GitTreeState, 55 | BuildDate: BuildDate, 56 | GoVersion: runtime.Version(), 57 | Compiler: runtime.Compiler, 58 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), 59 | } 60 | } 61 | 62 | // String returns the string representation of the version info 63 | func (i *VersionInfo) String() string { 64 | b := strings.Builder{} 65 | w := tabwriter.NewWriter(&b, 0, 0, 2, ' ', 0) 66 | 67 | fmt.Fprintf(w, "GitVersion:\t%s\n", i.GitVersion) 68 | fmt.Fprintf(w, "GitCommit:\t%s\n", i.GitCommit) 69 | fmt.Fprintf(w, "GitTreeState:\t%s\n", i.GitTreeState) 70 | fmt.Fprintf(w, "BuildDate:\t%s\n", i.BuildDate) 71 | fmt.Fprintf(w, "GoVersion:\t%s\n", i.GoVersion) 72 | fmt.Fprintf(w, "Compiler:\t%s\n", i.Compiler) 73 | fmt.Fprintf(w, "Platform:\t%s\n", i.Platform) 74 | 75 | w.Flush() 76 | return b.String() 77 | } 78 | -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: /api/v1 2 | consumes: 3 | - application/json 4 | definitions: 5 | configuration.Configuration: 6 | properties: 7 | credentials: 8 | type: string 9 | dev-mode: 10 | type: boolean 11 | disable-auth: 12 | type: boolean 13 | listen-address: 14 | description: DisplayMode string `json:"display-mode"` 15 | type: string 16 | listen-port: 17 | type: integer 18 | log-level: 19 | type: string 20 | redis-password: 21 | type: string 22 | redis-server: 23 | type: string 24 | ttl: 25 | type: integer 26 | type: object 27 | configuration.VersionInfo: 28 | properties: 29 | buildDate: 30 | type: string 31 | compiler: 32 | type: string 33 | gitCommit: 34 | type: string 35 | gitTreeState: 36 | type: string 37 | gitVersion: 38 | type: string 39 | goVersion: 40 | type: string 41 | platform: 42 | type: string 43 | type: object 44 | models.Event: 45 | properties: 46 | hostname: 47 | type: string 48 | output: 49 | type: string 50 | output_fields: 51 | additionalProperties: true 52 | type: object 53 | priority: 54 | type: string 55 | rule: 56 | type: string 57 | source: 58 | type: string 59 | tags: 60 | items: 61 | type: string 62 | type: array 63 | time: 64 | type: string 65 | uuid: 66 | type: string 67 | required: 68 | - output 69 | - priority 70 | - rule 71 | - time 72 | type: object 73 | models.Payload: 74 | properties: 75 | event: 76 | $ref: '#/definitions/models.Event' 77 | outputs: 78 | items: 79 | type: string 80 | type: array 81 | required: 82 | - event 83 | - outputs 84 | type: object 85 | models.ResultsCount: 86 | properties: 87 | statistics: 88 | $ref: '#/definitions/models.Statistics' 89 | type: object 90 | models.ResultsCountBy: 91 | properties: 92 | results: 93 | additionalProperties: 94 | type: integer 95 | type: object 96 | statistics: 97 | $ref: '#/definitions/models.Statistics' 98 | type: object 99 | models.ResultsSearch: 100 | properties: 101 | results: 102 | additionalProperties: 103 | $ref: '#/definitions/models.Event' 104 | type: object 105 | statistics: 106 | $ref: '#/definitions/models.Statistics' 107 | type: object 108 | models.Statistics: 109 | properties: 110 | all: 111 | type: integer 112 | distincts: 113 | type: integer 114 | returned: 115 | type: integer 116 | type: object 117 | host: :2802 118 | info: 119 | contact: 120 | email: cncf-falco-dev@lists.cncf.io 121 | name: Falco Authors 122 | url: https://github.com/falcosecurity 123 | description: Falcosidekick UI 124 | license: 125 | name: Apache 2.0 126 | url: http://www.apache.org/licenses/LICENSE-2.0.html 127 | title: Falcosidekick UI 128 | version: "1.0" 129 | paths: 130 | /api/v1/: 131 | post: 132 | consumes: 133 | - application/json 134 | description: Add Event 135 | parameters: 136 | - description: Payload 137 | in: body 138 | name: payload 139 | required: true 140 | schema: 141 | $ref: '#/definitions/models.Payload' 142 | produces: 143 | - application/json 144 | responses: 145 | "200": 146 | description: OK 147 | schema: 148 | type: string 149 | "500": 150 | description: Internal Server Error 151 | schema: 152 | type: string 153 | summary: Add Event 154 | /api/v1/authenticate: 155 | post: 156 | description: Authenticate 157 | responses: 158 | "200": 159 | description: authenticated 160 | schema: 161 | type: string 162 | "500": 163 | description: Internal Server Error 164 | schema: 165 | type: string 166 | summary: Authenticate 167 | /api/v1/configuration: 168 | get: 169 | description: Configuration 170 | produces: 171 | - application/json 172 | responses: 173 | "200": 174 | description: Configuration 175 | schema: 176 | $ref: '#/definitions/configuration.Configuration' 177 | "500": 178 | description: Internal Server Error 179 | schema: 180 | type: string 181 | summary: Configuration 182 | /api/v1/events/count: 183 | get: 184 | description: Count Events 185 | parameters: 186 | - description: pretty 187 | in: query 188 | name: pretty 189 | type: boolean 190 | - description: priority 191 | in: query 192 | name: priority 193 | type: string 194 | - description: source 195 | in: query 196 | name: source 197 | type: string 198 | - description: filter 199 | in: query 200 | name: filter 201 | type: string 202 | - description: rule 203 | in: query 204 | name: rule 205 | type: string 206 | - description: tags 207 | in: query 208 | name: tags 209 | type: string 210 | - description: since 211 | in: query 212 | name: since 213 | type: integer 214 | produces: 215 | - application/json 216 | responses: 217 | "200": 218 | description: Count Events Result 219 | schema: 220 | $ref: '#/definitions/models.ResultsCount' 221 | "400": 222 | description: Bad Request 223 | schema: 224 | type: string 225 | summary: Count Events 226 | /api/v1/events/count/:groupby: 227 | get: 228 | description: Count Events By 229 | parameters: 230 | - description: group By 231 | in: path 232 | name: groupby 233 | required: true 234 | type: string 235 | - description: pretty 236 | in: query 237 | name: pretty 238 | type: boolean 239 | - description: priority 240 | in: query 241 | name: priority 242 | type: string 243 | - description: source 244 | in: query 245 | name: source 246 | type: string 247 | - description: filter 248 | in: query 249 | name: filter 250 | type: string 251 | - description: rule 252 | in: query 253 | name: rule 254 | type: string 255 | - description: tags 256 | in: query 257 | name: tags 258 | type: string 259 | - description: since 260 | in: query 261 | name: since 262 | type: integer 263 | produces: 264 | - application/json 265 | responses: 266 | "200": 267 | description: Count Events By Result 268 | schema: 269 | $ref: '#/definitions/models.ResultsCountBy' 270 | "400": 271 | description: Bad Request 272 | schema: 273 | type: string 274 | summary: Count Events By 275 | /api/v1/events/search: 276 | get: 277 | description: Search Events 278 | parameters: 279 | - description: pretty 280 | in: query 281 | name: pretty 282 | type: boolean 283 | - description: priority 284 | in: query 285 | name: priority 286 | type: string 287 | - description: source 288 | in: query 289 | name: source 290 | type: string 291 | - description: filter 292 | in: query 293 | name: filter 294 | type: string 295 | - description: rule 296 | in: query 297 | name: rule 298 | type: string 299 | - description: tags 300 | in: query 301 | name: tags 302 | type: string 303 | - description: since 304 | in: query 305 | name: since 306 | type: integer 307 | produces: 308 | - application/json 309 | responses: 310 | "200": 311 | description: Search Events Result 312 | schema: 313 | $ref: '#/definitions/models.ResultsSearch' 314 | "400": 315 | description: Bad Request 316 | schema: 317 | type: string 318 | summary: Search Events 319 | /api/v1/healthz: 320 | get: 321 | description: Healthcheck 322 | produces: 323 | - application/json 324 | responses: 325 | "200": 326 | description: '{\"ok\"}' 327 | schema: 328 | type: string 329 | "500": 330 | description: Internal Server Error 331 | schema: 332 | type: string 333 | summary: Healthcheck 334 | /api/v1/outputs: 335 | get: 336 | description: Healthcheck 337 | produces: 338 | - application/json 339 | responses: 340 | "200": 341 | description: Outputs 342 | schema: 343 | items: 344 | type: string 345 | type: array 346 | "500": 347 | description: Internal Server Error 348 | schema: 349 | type: string 350 | summary: List Outputs 351 | /api/v1/version: 352 | get: 353 | description: Version 354 | produces: 355 | - application/json 356 | responses: 357 | "200": 358 | description: Version 359 | schema: 360 | $ref: '#/definitions/configuration.VersionInfo' 361 | "500": 362 | description: Internal Server Error 363 | schema: 364 | type: string 365 | summary: Version 366 | produces: 367 | - application/json 368 | schemes: 369 | - http 370 | swagger: "2.0" 371 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@vue/app" 4 | ] 5 | } -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | VUE_APP_API="http://localhost:2802" -------------------------------------------------------------------------------- /frontend/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "plugin:vue/essential", 5 | "@vue/airbnb" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | _site 5 | .vscode -------------------------------------------------------------------------------- /frontend/.yarnclean: -------------------------------------------------------------------------------- 1 | # test directories 2 | __tests__ 3 | test 4 | tests 5 | powered-test 6 | 7 | # asset directories 8 | docs 9 | doc 10 | website 11 | images 12 | assets 13 | 14 | # examples 15 | example 16 | examples 17 | 18 | # code coverage directories 19 | coverage 20 | .nyc_output 21 | 22 | # build scripts 23 | Makefile 24 | Gulpfile.js 25 | Gruntfile.js 26 | 27 | # configs 28 | appveyor.yml 29 | circle.yml 30 | codeship-services.yml 31 | codeship-steps.yml 32 | wercker.yml 33 | .tern-project 34 | .gitattributes 35 | .editorconfig 36 | .*ignore 37 | .eslintrc 38 | .jshintrc 39 | .flowconfig 40 | .documentup.json 41 | .yarn-metadata.json 42 | .travis.yml 43 | 44 | # misc 45 | *.md 46 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Falcosidekick UI 2 | 3 | Requirements: 4 | * `nodejs` >= v14 5 | * `yarn` >= 1.22 6 | 7 | ## Project setup 8 | 9 | ``` 10 | yarn install 11 | ``` 12 | 13 | ## Development Prerequiements 14 | 15 | * Run Redis docker: `docker run -d -p 6379:6379 redislabs/redisearch:2.2.4` 16 | * Run the Falcosidekick-ui backend: `go run . -x` or `./falcosidekick-ui -x` 17 | 18 | ### Compiles and hot-reloads for development 19 | 20 | ``` 21 | yarn serve 22 | ``` 23 | 24 | ### Compiles and minifies for production 25 | 26 | ``` 27 | yarn build 28 | ``` 29 | 30 | ### Lints and fixes files 31 | 32 | ``` 33 | yarn lint 34 | ``` 35 | 36 | ### 37 | 38 | Access: [`http://localhost:8080`](http://localhost:8080) 39 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "falcosidekick-ui", 3 | "description": "A simple WebUI with latest events from Falco ", 4 | "author": "Falco Authors", 5 | "private": true, 6 | "scripts": { 7 | "serve": "export NODE_OPTIONS=--openssl-legacy-provider; vue-cli-service serve", 8 | "build": "export NODE_OPTIONS=--openssl-legacy-provider; vue-cli-service build", 9 | "test": "vue-cli-service test", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "@mdi/font": "^6.6.96", 14 | "@vue/cli-plugin-babel": "^3.3.0", 15 | "@vue/cli-plugin-eslint": "^3.3.0", 16 | "@vue/cli-plugin-pwa": "^3.3.0", 17 | "@vue/cli-service": "^3.3.0", 18 | "@vue/eslint-config-airbnb": "^3.0.5", 19 | "axios": "^1.8.2", 20 | "chai": "^4.2.0", 21 | "chart.js": "^3.7.1", 22 | "chartjs-adapter-moment": "^1.0.0", 23 | "cors": "^2.8.5", 24 | "dayjs": "^1.11.1", 25 | "lint-staged": "^7.3.0", 26 | "material-design-icons-iconfont": "^6.5.0", 27 | "moment": "^2.29.4", 28 | "numeral": "^2.0.6", 29 | "register-service-worker": "^1.5.2", 30 | "sass": "^1.49.11", 31 | "sass-loader": "^10", 32 | "socket.io": "^4.5.1", 33 | "socket.io-client": "^4.5.1", 34 | "vue": "^2.5.21", 35 | "vue-chartjs": "^4.0.5", 36 | "vue-cli-plugin-vuetify": "^2.4.8", 37 | "vue-json-pretty": "^1.9.4", 38 | "vue-router": "^3.0.2", 39 | "vue-template-compiler": "^2.5.21", 40 | "vuetify": "^2.6.10", 41 | "vuetify-loader": "^1.7.3", 42 | "vuex": "3", 43 | "vuex-persistedstate": "^4.1.0", 44 | "webpack": "^4.28.4", 45 | "ws": "^8.8.1" 46 | }, 47 | "devDependencies": { 48 | "style-loader": "^0.23.1", 49 | "stylus": "^0.57.0", 50 | "stylus-loader": "^3.0.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falcosecurity/falcosidekick-ui/f032d496d5d16a5ce689a2ded0944e5604410e04/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falcosecurity/falcosidekick-ui/f032d496d5d16a5ce689a2ded0944e5604410e04/frontend/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falcosecurity/falcosidekick-ui/f032d496d5d16a5ce689a2ded0944e5604410e04/frontend/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Falcosidekick UI 10 | 11 | 12 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Falcosidekick UI", 3 | "short_name": "Falcosidekick UI", 4 | "icons": [ 5 | { 6 | "src": "/img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/index.html", 17 | "display": "standalone", 18 | "background_color": "#000000", 19 | "theme_color": "#3f51b5 " 20 | } -------------------------------------------------------------------------------- /frontend/public/v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falcosecurity/falcosidekick-ui/f032d496d5d16a5ce689a2ded0944e5604410e04/frontend/public/v.png -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 141 | 142 | 155 | -------------------------------------------------------------------------------- /frontend/src/components/charts/bar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 156 | -------------------------------------------------------------------------------- /frontend/src/components/charts/pie.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 168 | -------------------------------------------------------------------------------- /frontend/src/components/charts/timeline.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 298 | -------------------------------------------------------------------------------- /frontend/src/components/counters.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 115 | -------------------------------------------------------------------------------- /frontend/src/components/filters.vue: -------------------------------------------------------------------------------- 1 | 223 | 224 | 456 | -------------------------------------------------------------------------------- /frontend/src/css/main.css: -------------------------------------------------------------------------------- 1 | .intro { 2 | display: flex; 3 | justify-content: center; 4 | margin: 4rem 0; 5 | } 6 | 7 | a { 8 | color: inherit; 9 | } 10 | 11 | .text-center { 12 | text-align: center; 13 | } -------------------------------------------------------------------------------- /frontend/src/http.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | 16 | import axios from 'axios'; 17 | import store from './store'; 18 | 19 | const production = process.env.NODE_ENV === 'production'; 20 | 21 | const api = axios.create({ 22 | baseURL: `${production ? `//${window.location.host}${window.location.pathname}` : process.env.VUE_APP_API}api/v1`, 23 | headers: { 24 | 'Content-type': 'application/json', 25 | 'Access-Control-Allow-Origin': '*', 26 | 'Access-Control-Allow-Methods': '*', 27 | }, 28 | params: new URLSearchParams(), 29 | }); 30 | 31 | export const requests = { 32 | listOutputs() { 33 | return api.request({ 34 | url: '/outputs', 35 | method: 'get', 36 | params: {}, 37 | auth: { 38 | username: store.state.username, 39 | password: store.state.password, 40 | }, 41 | }); 42 | }, 43 | getConfiguration() { 44 | return api.request({ 45 | url: '/configuration', 46 | method: 'get', 47 | params: {}, 48 | auth: { 49 | username: store.state.username, 50 | password: store.state.password, 51 | }, 52 | }); 53 | }, 54 | getVersion() { 55 | return api.request({ 56 | url: '/version', 57 | method: 'get', 58 | params: {}, 59 | auth: { 60 | username: store.state.username, 61 | password: store.state.password, 62 | }, 63 | }); 64 | }, 65 | countEvents(source, hostname, priority, rule, filter, tags, since) { 66 | return api.request({ 67 | url: '/events/count', 68 | method: 'get', 69 | params: { 70 | source: `${source}`, 71 | hostname: `${hostname}`, 72 | priority: `${priority}`, 73 | rule: `${rule}`, 74 | filter: `${filter}`, 75 | tags: `${tags}`, 76 | since: `${since}`, 77 | }, 78 | auth: { 79 | username: store.state.username, 80 | password: store.state.password, 81 | }, 82 | }); 83 | }, 84 | countByEvents(group, source, hostname, priority, rule, filter, tags, since) { 85 | return api.request({ 86 | url: `/events/count/${group}`, 87 | method: 'get', 88 | params: { 89 | source: `${source}`, 90 | hostname: `${hostname}`, 91 | priority: `${priority}`, 92 | rule: `${rule}`, 93 | filter: `${filter}`, 94 | tags: `${tags}`, 95 | since: `${since}`, 96 | }, 97 | auth: { 98 | username: store.state.username, 99 | password: store.state.password, 100 | }, 101 | }); 102 | }, 103 | searchEvents(source, hostname, priority, rule, filter, tags, since, page, limit) { 104 | return api.request({ 105 | url: '/events/search', 106 | method: 'get', 107 | params: { 108 | source: `${source}`, 109 | hostname: `${hostname}`, 110 | priority: `${priority}`, 111 | rule: `${rule}`, 112 | filter: `${filter}`, 113 | tags: `${tags}`, 114 | since: `${since}`, 115 | page: `${page}`, 116 | limit: `${limit}`, 117 | }, 118 | auth: { 119 | username: store.state.username, 120 | password: store.state.password, 121 | }, 122 | }); 123 | }, 124 | authenticate(username, password) { 125 | return api.request({ 126 | url: '/auth', 127 | method: 'post', 128 | auth: { 129 | username: `${username}`, 130 | password: `${password}`, 131 | }, 132 | }); 133 | }, 134 | }; 135 | 136 | export default { 137 | }; 138 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | import Vue from 'vue'; 16 | import Vuetify from 'vuetify'; 17 | import moment from 'moment'; 18 | import 'vuetify/dist/vuetify.min.css'; 19 | import '@mdi/font/css/materialdesignicons.css'; 20 | import App from './App.vue'; 21 | import router from './router'; 22 | import store from './store'; 23 | 24 | const capitalize = function capitalize(value) { 25 | if (!value) return ''; 26 | return value.charAt(0).toUpperCase() + value.slice(1); 27 | }; 28 | 29 | const formatDate = function formatDate(value) { 30 | if (!value) return ''; 31 | return moment(String(value)).format('YYYY/MM/DD HH:mm:ss:SSS'); 32 | }; 33 | 34 | const opts = {}; 35 | const vuetify = new Vuetify(opts); 36 | 37 | Vue.filter('formatDate', formatDate); 38 | Vue.filter('capitalize', capitalize); 39 | 40 | Vue.use(Vuetify); 41 | 42 | new Vue({ 43 | vuetify, 44 | router, 45 | store, 46 | components: { App }, 47 | render: h => h(App), 48 | }).$mount('#app'); 49 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | import Vue from 'vue'; 16 | import VueRouter from 'vue-router'; 17 | import TestPage from '../views/TestPage.vue'; 18 | import Dashboard from '../views//DashboardPage.vue'; 19 | import EventsPage from '../views/EventsPage.vue'; 20 | import InfoPage from '../views/InfoPage.vue'; 21 | import LoginPage from '../views/LoginPage.vue'; 22 | import store from '../store'; 23 | 24 | Vue.use(VueRouter); 25 | 26 | const routes = [ 27 | { 28 | path: '/test', 29 | name: 'test', 30 | component: TestPage, 31 | }, 32 | { 33 | path: '/dashboard', 34 | name: 'dashboard', 35 | component: Dashboard, 36 | }, 37 | { 38 | path: '/events', 39 | name: 'events', 40 | component: EventsPage, 41 | }, 42 | { 43 | path: '/info', 44 | name: 'info', 45 | component: InfoPage, 46 | }, 47 | { 48 | path: '/login', 49 | name: 'login', 50 | component: LoginPage, 51 | }, 52 | { 53 | path: '*', 54 | redirect: { 55 | name: 'dashboard', 56 | }, 57 | }, 58 | ]; 59 | 60 | const router = new VueRouter({ 61 | mode: 'hash', 62 | base: process.env.BASE_URL, 63 | routes, 64 | }); 65 | 66 | router.beforeEach((to, from, next) => { 67 | if (store.state.username === '' || store.state.password === '') { 68 | if (to.name !== 'login') { 69 | router.push('/login'); 70 | } 71 | } 72 | next(); 73 | }); 74 | 75 | export default router; 76 | -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | /* eslint-disable no-param-reassign */ 16 | import Vue from 'vue'; 17 | import Vuex from 'vuex'; 18 | import createPersistedState from 'vuex-persistedstate'; 19 | 20 | Vue.use(Vuex); 21 | 22 | export default new Vuex.Store({ 23 | plugins: [createPersistedState({ 24 | storage: window.sessionStorage, 25 | })], 26 | state: { 27 | ticer: 0, 28 | refreshInterval: 10000, 29 | refreshIntervals: ['off', '10s', '20s', '30s', '1min', '2min'], 30 | username: '', 31 | password: '', 32 | }, 33 | mutations: { 34 | increment(state) { 35 | state.ticer += 1; 36 | }, 37 | setRefreshInterval(state, payload) { 38 | switch (payload) { 39 | case '10s': 40 | state.refreshInterval = 10000; 41 | break; 42 | case '20s': 43 | state.refreshInterval = 20000; 44 | break; 45 | case '30s': 46 | state.refreshInterval = 30000; 47 | break; 48 | case '1min': 49 | state.refreshInterval = 60000; 50 | break; 51 | case '2min': 52 | state.refreshInterval = 120000; 53 | break; 54 | case 'off': 55 | state.refreshInterval = 0; 56 | break; 57 | default: 58 | break; 59 | } 60 | }, 61 | setCredentials(state, payload) { 62 | state.username = payload.username; 63 | state.password = payload.password; 64 | }, 65 | emptyCredentials(state) { 66 | state.username = ''; 67 | state.password = ''; 68 | }, 69 | }, 70 | actions: { 71 | increment(context) { 72 | context.commit('increment'); 73 | }, 74 | setRefreshInterval(context, payload) { 75 | context.commit('setRefreshInterval', payload); 76 | }, 77 | setCredentials(context, payload) { 78 | context.commit('setCredentials', payload); 79 | }, 80 | emptyCredentials(context) { 81 | context.commit('emptyCredentials'); 82 | }, 83 | }, 84 | }); 85 | /* eslint-enable no-param-reassign */ 86 | -------------------------------------------------------------------------------- /frontend/src/utils.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | const crypto = require('crypto'); 16 | 17 | export const utils = { 18 | priorityToColor(priority) { 19 | switch (priority) { 20 | case 'Emergency': 21 | return '#C62828'; 22 | case 'Alert': 23 | return '#D32F2F'; 24 | case 'Critical': 25 | return '#E53935'; 26 | case 'Error': 27 | return '#FF5252'; 28 | case 'Warning': 29 | return '#FB8C00'; 30 | case 'Notice': 31 | return '#1976D2'; 32 | case 'Informational': 33 | return '#03A9F4'; 34 | case 'Debug': 35 | return '#29B6F6'; 36 | default: 37 | return '#555'; 38 | } 39 | }, 40 | stringToColor(str) { 41 | return `#${crypto.createHash('md5').update(str).digest('hex').substring(0, 6)}`; 42 | }, 43 | }; 44 | 45 | export default { 46 | }; 47 | -------------------------------------------------------------------------------- /frontend/src/views/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'vue/multi-word-component-names': 0, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/views/DashboardPage.vue: -------------------------------------------------------------------------------- 1 | 102 | 103 | 147 | 148 | -------------------------------------------------------------------------------- /frontend/src/views/EventsPage.vue: -------------------------------------------------------------------------------- 1 | 232 | 233 | 424 | 425 | 440 | -------------------------------------------------------------------------------- /frontend/src/views/InfoPage.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 119 | 120 | -------------------------------------------------------------------------------- /frontend/src/views/LoginPage.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 106 | 107 | -------------------------------------------------------------------------------- /frontend/src/views/TestPage.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 42 | 43 | -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | module.exports = { 15 | publicPath: './', 16 | }; 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/falcosecurity/falcosidekick-ui 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/Issif/redisearch-go v1.1.2-0.20220629142418-f66689e2ff5c 7 | github.com/go-playground/validator/v10 v10.15.0 8 | github.com/gomodule/redigo v1.8.9 9 | github.com/labstack/echo/v4 v4.11.1 10 | github.com/swaggo/echo-swagger v1.4.0 11 | github.com/swaggo/swag v1.16.1 12 | ) 13 | 14 | require ( 15 | github.com/KyleBanks/depth v1.2.1 // indirect 16 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 17 | github.com/go-openapi/jsonpointer v0.20.0 // indirect 18 | github.com/go-openapi/jsonreference v0.20.2 // indirect 19 | github.com/go-openapi/spec v0.20.9 // indirect 20 | github.com/go-openapi/swag v0.22.4 // indirect 21 | github.com/go-playground/locales v0.14.1 // indirect 22 | github.com/go-playground/universal-translator v0.18.1 // indirect 23 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect 24 | github.com/josharian/intern v1.0.0 // indirect 25 | github.com/labstack/gommon v0.4.0 // indirect 26 | github.com/leodido/go-urn v1.2.4 // indirect 27 | github.com/mailru/easyjson v0.7.7 // indirect 28 | github.com/mattn/go-colorable v0.1.13 // indirect 29 | github.com/mattn/go-isatty v0.0.19 // indirect 30 | github.com/rogpeppe/go-internal v1.11.0 // indirect 31 | github.com/swaggo/files/v2 v2.0.0 // indirect 32 | github.com/valyala/bytebufferpool v1.0.0 // indirect 33 | github.com/valyala/fasttemplate v1.2.2 // indirect 34 | golang.org/x/crypto v0.31.0 // indirect 35 | golang.org/x/net v0.33.0 // indirect 36 | golang.org/x/sys v0.28.0 // indirect 37 | golang.org/x/text v0.21.0 // indirect 38 | golang.org/x/time v0.3.0 // indirect 39 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 40 | gopkg.in/yaml.v3 v3.0.1 // indirect 41 | ) 42 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Issif/redisearch-go v1.1.2-0.20220629142418-f66689e2ff5c h1:k6V2ixK2I6NykJMiCj+O8+2xsfBttF2QxQjlPpzPTNg= 2 | github.com/Issif/redisearch-go v1.1.2-0.20220629142418-f66689e2ff5c/go.mod h1:UQGs8ZvEw5/QCKkFEMMiarV7yALg32yVqwB0tq0x7y8= 3 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 4 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 5 | github.com/RediSearch/redisearch-go v1.1.1 h1:YElqguUO9lSqCYszrQcoTUoB9zBRyb2gkO4+yh3STMo= 6 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 11 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 12 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 13 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 14 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 15 | github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= 16 | github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= 17 | github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= 18 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 19 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 20 | github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= 21 | github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= 22 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 23 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 24 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 25 | github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= 26 | github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 27 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 28 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 29 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 30 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 31 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 32 | github.com/go-playground/validator/v10 v10.15.0 h1:nDU5XeOKtB3GEa+uB7GNYwhVKsgjAR7VgKoNB6ryXfw= 33 | github.com/go-playground/validator/v10 v10.15.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 34 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 35 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 36 | github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= 37 | github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= 38 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 39 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 40 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 41 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 42 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 43 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 44 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 45 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 46 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 47 | github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= 48 | github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= 49 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= 50 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= 51 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 52 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 53 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 54 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 55 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 56 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 57 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 58 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 59 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 60 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 61 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 62 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 63 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 64 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 65 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 66 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 67 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 68 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 69 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 70 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 71 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 72 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 73 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 74 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 75 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 76 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 77 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 78 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 79 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 80 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 81 | github.com/swaggo/echo-swagger v1.4.0 h1:RCxLKySw1SceHLqnmc41pKyiIeE+OiD7NSI7FUOBlLo= 82 | github.com/swaggo/echo-swagger v1.4.0/go.mod h1:Wh3VlwjZGZf/LH0s81tz916JokuPG7y/ZqaqnckYqoQ= 83 | github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= 84 | github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= 85 | github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg= 86 | github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto= 87 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 88 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 89 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 90 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 91 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 92 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 93 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 94 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 95 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 96 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 97 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 98 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 99 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 100 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 102 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 104 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 105 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 106 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 107 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 108 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 109 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 110 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 111 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 112 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 113 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 114 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 115 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 116 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 117 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 118 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 119 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 120 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 121 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 122 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 123 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 124 | -------------------------------------------------------------------------------- /imgs/webui_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falcosecurity/falcosidekick-ui/f032d496d5d16a5ce689a2ded0944e5604410e04/imgs/webui_01.png -------------------------------------------------------------------------------- /imgs/webui_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falcosecurity/falcosidekick-ui/f032d496d5d16a5ce689a2ded0944e5604410e04/imgs/webui_02.png -------------------------------------------------------------------------------- /imgs/webui_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falcosecurity/falcosidekick-ui/f032d496d5d16a5ce689a2ded0944e5604410e04/imgs/webui_03.png -------------------------------------------------------------------------------- /imgs/webui_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falcosecurity/falcosidekick-ui/f032d496d5d16a5ce689a2ded0944e5604410e04/imgs/webui_04.png -------------------------------------------------------------------------------- /imgs/webui_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falcosecurity/falcosidekick-ui/f032d496d5d16a5ce689a2ded0944e5604410e04/imgs/webui_05.png -------------------------------------------------------------------------------- /internal/api/api.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package api 16 | 17 | import ( 18 | "crypto/subtle" 19 | "encoding/base64" 20 | "fmt" 21 | "net/http" 22 | "strings" 23 | 24 | "github.com/falcosecurity/falcosidekick-ui/configuration" 25 | "github.com/falcosecurity/falcosidekick-ui/internal/events" 26 | "github.com/falcosecurity/falcosidekick-ui/internal/models" 27 | "github.com/falcosecurity/falcosidekick-ui/internal/utils" 28 | echo "github.com/labstack/echo/v4" 29 | ) 30 | 31 | func returnResult(c echo.Context, r models.Results, err error) error { 32 | if err != nil { 33 | utils.WriteLog("error", err.Error()) 34 | return echo.NewHTTPError(http.StatusBadRequest, err.Error()) 35 | } 36 | if c.QueryParam("pretty") == "true" { 37 | return c.JSONPretty(http.StatusOK, r, " ") 38 | } 39 | return c.JSON(http.StatusOK, r) 40 | } 41 | 42 | // Add Event 43 | // @Summary Add Event 44 | // @Description Add Event 45 | // @Accept json 46 | // @Produce json 47 | // @param payload body models.Payload true "Payload" 48 | // @Success 200 {string} string "" 49 | // @Failure 500 {string} http.StatusInternalServerError "Internal Server Error" 50 | // @Router /api/v1/ [post] 51 | func AddEvent(c echo.Context) error { 52 | payload := new(models.Payload) 53 | if err := c.Bind(payload); err != nil { 54 | utils.WriteLog("error", err.Error()) 55 | return err 56 | } 57 | 58 | if err := c.Validate(payload); err != nil { 59 | utils.WriteLog("error", err.Error()) 60 | return err 61 | } 62 | 63 | models.GetOutputs().Update(payload.Outputs) 64 | 65 | payload.Event.Output = utils.TrimPrefix(payload.Event.Output) 66 | 67 | if err := events.Add(&payload.Event); err != nil { 68 | return err 69 | } 70 | return c.JSON(http.StatusOK, "{}") 71 | } 72 | 73 | // Count Events 74 | // @Summary Count Events 75 | // @Description Count Events 76 | // @Param pretty query bool false "pretty" 77 | // @Param priority query string false "priority" 78 | // @Param source query string false "source" 79 | // @Param filter query string false "filter" 80 | // @Param rule query string false "rule" 81 | // @Param tags query string false "tags" 82 | // @Param since query int false "since" 83 | // @Produce json 84 | // @Success 200 {object} models.ResultsCount "Count Events Result" 85 | // @Failure 400 {string} http.StatusBadRequest "Bad Request" 86 | // @Router /api/v1/events/count [get] 87 | func CountEvent(c echo.Context) error { 88 | a := models.GetArguments(c) 89 | utils.WriteLog("debug", fmt.Sprintf("GET count (source='%v', priority='%v', rule='%v', since='%v', hostname= '%v', filter='%v', tags='%v')", a.Source, a.Priority, a.Rule, a.Since, a.Hostname, a.Filter, a.Tags)) 90 | 91 | r, err := events.Count(a) 92 | return returnResult(c, r, err) 93 | } 94 | 95 | // Count Events By 96 | // @Summary Count Events By 97 | // @Description Count Events By 98 | // @Param groupby path string true "group By" 99 | // @Param pretty query bool false "pretty" 100 | // @Param priority query string false "priority" 101 | // @Param source query string false "source" 102 | // @Param filter query string false "filter" 103 | // @Param rule query string false "rule" 104 | // @Param tags query string false "tags" 105 | // @Param since query int false "since" 106 | // @Produce json 107 | // @Success 200 {object} models.ResultsCountBy "Count Events By Result" 108 | // @Failure 400 {string} http.StatusBadRequest "Bad Request" 109 | // @Router /api/v1/events/count/:groupby [get] 110 | func CountByEvent(c echo.Context) error { 111 | a := models.GetArguments(c) 112 | utils.WriteLog("debug", fmt.Sprintf("GET count by %v (source='%v', priority='%v', rule='%v', since='%v', hostname='%v', filter='%v', tags='%v')", a.GroupBy, a.Source, a.Priority, a.Rule, a.Since, a.Hostname, a.Filter, a.Tags)) 113 | 114 | r, err := events.CountBy(a) 115 | return returnResult(c, r, err) 116 | } 117 | 118 | // Search Events 119 | // @Summary Search Events 120 | // @Description Search Events 121 | // @Param pretty query bool false "pretty" 122 | // @Param priority query string false "priority" 123 | // @Param source query string false "source" 124 | // @Param filter query string false "filter" 125 | // @Param rule query string false "rule" 126 | // @Param tags query string false "tags" 127 | // @Param since query int false "since" 128 | // @Produce json 129 | // @Success 200 {object} models.ResultsSearch "Search Events Result" 130 | // @Failure 400 {string} http.StatusBadRequest "Bad Request" 131 | // @Router /api/v1/events/search [get] 132 | func Search(c echo.Context) error { 133 | a := models.GetArguments(c) 134 | utils.WriteLog("debug", fmt.Sprintf("GET search (source='%v', priority='%v', rule='%v', since='%v', hostname='%v', filter='%v', tags='%v', page='%v', limit='%v')", a.Source, a.Priority, a.Rule, a.Since, a.Hostname, a.Filter, a.Tags, a.Page, a.Limit)) 135 | 136 | r, err := events.Search(a) 137 | return returnResult(c, r, err) 138 | } 139 | 140 | // Healthcheck 141 | // @Summary Healthcheck 142 | // @Description Healthcheck 143 | // @Produce json 144 | // @Success 200 {string} json "{\"ok\"}" 145 | // @Router /api/v1/healthz [get] 146 | // @Failure 500 {string} http.StatusInternalServerError "Internal Server Error" 147 | func Healthz(c echo.Context) error { 148 | msg := make(map[string]string) 149 | msg["status"] = "ok" 150 | return c.JSON(http.StatusOK, msg) 151 | } 152 | 153 | // List Outputs 154 | // @Summary List Outputs 155 | // @Description Healthcheck 156 | // @Produce json 157 | // @Success 200 {object} models.Outputs "Outputs" 158 | // @Failure 500 {string} http.StatusInternalServerError "Internal Server Error" 159 | // @Router /api/v1/outputs [get] 160 | func GetOutputs(c echo.Context) error { 161 | utils.WriteLog("debug", "GET outputs") 162 | return c.JSON(http.StatusOK, models.GetOutputs()) 163 | } 164 | 165 | // Configuration 166 | // @Summary Configuration 167 | // @Description Configuration 168 | // @Produce json 169 | // @Success 200 {object} configuration.Configuration "Configuration" 170 | // @Failure 500 {string} http.StatusInternalServerError "Internal Server Error" 171 | // @Router /api/v1/configuration [get] 172 | func GetConfiguration(c echo.Context) error { 173 | utils.WriteLog("debug", "GET config") 174 | config := configuration.GetConfiguration() 175 | obfuscatedConfig := *config 176 | obfuscatedConfig.Credentials = strings.Split(config.Credentials, ":")[0] + ":*******" 177 | if obfuscatedConfig.RedisPassword != "" { 178 | obfuscatedConfig.RedisPassword = "*******" 179 | } 180 | return c.JSON(http.StatusOK, obfuscatedConfig) 181 | } 182 | 183 | // Version 184 | // @Summary Version 185 | // @Description Version 186 | // @Produce json 187 | // @Success 200 {object} configuration.VersionInfo "Version" 188 | // @Failure 500 {string} http.StatusInternalServerError "Internal Server Error" 189 | // @Router /api/v1/version [get] 190 | func GetVersionInfo(c echo.Context) error { 191 | utils.WriteLog("debug", "GET version") 192 | return c.JSON(http.StatusOK, configuration.GetVersionInfo()) 193 | } 194 | 195 | // Authenticate 196 | // @Summary Authenticate 197 | // @Description Authenticate 198 | // @Success 200 {string} string "authenticated" 199 | // @Failure 500 {string} http.StatusInternalServerError "Internal Server Error" 200 | // @Router /api/v1/authenticate [post] 201 | // @securityDefinitions.basic BasicAuth 202 | func Authenticate(c echo.Context) error { 203 | authHeader := c.Request().Header["Authorization"] 204 | config := configuration.GetConfiguration() 205 | if config.DisableAuth { 206 | return c.JSON(http.StatusOK, "authorized") 207 | } 208 | if len(authHeader) == 0 { 209 | utils.WriteLog("warning", "user '' unknown or wrong password") 210 | return c.JSON(http.StatusUnauthorized, "unauthorized") 211 | } 212 | t := strings.Split(authHeader[0], " ") 213 | if len(t) != 2 { 214 | utils.WriteLog("warning", "user '' unknown or wrong password") 215 | return c.JSON(http.StatusUnauthorized, "unauthorized") 216 | } 217 | u, err := base64.StdEncoding.DecodeString(t[1]) 218 | if err != nil { 219 | utils.WriteLog("warning", "user '' unknown or wrong password") 220 | return c.JSON(http.StatusUnauthorized, "unauthorized") 221 | } 222 | v := strings.Split(string(u), ":")[0] 223 | if subtle.ConstantTimeCompare( 224 | u, 225 | []byte(config.Credentials), 226 | ) == 1 { 227 | utils.WriteLog("info", fmt.Sprintf("user '%v' authenticated", v)) 228 | return c.JSON(http.StatusOK, "authorized") 229 | } 230 | if v != "anonymous" { 231 | utils.WriteLog("warning", fmt.Sprintf("user '%v' unknown or wrong password", v)) 232 | } 233 | return c.JSON(http.StatusUnauthorized, "unauthorized") 234 | } 235 | -------------------------------------------------------------------------------- /internal/database/redis/client.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package redis 16 | 17 | import ( 18 | "github.com/falcosecurity/falcosidekick-ui/configuration" 19 | 20 | "github.com/Issif/redisearch-go/redisearch" 21 | "github.com/gomodule/redigo/redis" 22 | ) 23 | 24 | var client *redisearch.Client 25 | 26 | func CreateClient() *redisearch.Client { 27 | config := configuration.GetConfiguration() 28 | if config.RedisPassword != "" { 29 | pool := &redis.Pool{Dial: func() (redis.Conn, error) { 30 | return redis.Dial("tcp", config.RedisServer, redis.DialPassword(config.RedisPassword)) 31 | }} 32 | client = redisearch.NewClientFromPool(pool, "search-client-1") 33 | } else { 34 | client = redisearch.NewClient(config.RedisServer, "eventIndex") 35 | } 36 | return client 37 | } 38 | 39 | func GetClient() *redisearch.Client { 40 | return client 41 | } 42 | -------------------------------------------------------------------------------- /internal/database/redis/count.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "strconv" 21 | "strings" 22 | 23 | "github.com/Issif/redisearch-go/redisearch" 24 | "github.com/falcosecurity/falcosidekick-ui/internal/models" 25 | "github.com/falcosecurity/falcosidekick-ui/internal/utils" 26 | ) 27 | 28 | func CountKey(client *redisearch.Client, args *models.Arguments) (models.Results, error) { 29 | query := redisearch.NewQuery(newQuery(args)) 30 | count, _, err := client.AggregateQuery(redisearch.NewAggregateQuery().SetQuery(query)) 31 | if err != nil { 32 | return models.Results{}, err 33 | } 34 | 35 | return models.Results{ 36 | Stats: models.Statistics{ 37 | All: int64(count), 38 | }, 39 | }, nil 40 | } 41 | 42 | func CountKeyBy(client *redisearch.Client, args *models.Arguments) (models.Results, error) { 43 | if (args.GroupBy != "priority" && args.GroupBy != "rule" && args.GroupBy != "source" && args.GroupBy != "tags" && args.GroupBy != "hostname") || args.GroupBy == "" { 44 | return models.Results{}, errors.New("wrong Group By field") 45 | } 46 | reducer := redisearch.NewReducer("COUNT", []string{}) 47 | groupBy := redisearch.NewGroupBy().AddFields("@" + args.GroupBy).Reduce(*reducer) 48 | query := redisearch.NewQuery(newQuery(args)) 49 | 50 | _, results, err := client.AggregateQuery(redisearch.NewAggregateQuery().SetQuery(query).GroupBy(*groupBy)) 51 | if err != nil { 52 | return models.Results{}, err 53 | } 54 | 55 | ag := make(models.Aggregation) 56 | var all int64 57 | for _, i := range results { 58 | if i["__generated_aliascount"] == nil { 59 | return models.Results{}, fmt.Errorf("error result from redis for GroupBy %v", args.GroupBy) 60 | } 61 | key := i[args.GroupBy] 62 | count, _ := strconv.ParseInt(i["__generated_aliascount"].(string), 10, 64) 63 | switch key.(type) { 64 | case string: 65 | default: 66 | continue 67 | } 68 | if len(key.(string)) == 0 { 69 | continue 70 | } 71 | for _, j := range strings.Split(key.(string), ",") { 72 | ag[utils.UnEscape(j)] += count 73 | all += count 74 | } 75 | } 76 | 77 | return models.Results{ 78 | Stats: models.Statistics{ 79 | All: all, 80 | Distinct: int64(len(ag)), 81 | }, 82 | Results: ag, 83 | }, nil 84 | } 85 | -------------------------------------------------------------------------------- /internal/database/redis/index.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package redis 16 | 17 | import ( 18 | "github.com/Issif/redisearch-go/redisearch" 19 | utils "github.com/falcosecurity/falcosidekick-ui/internal/utils" 20 | ) 21 | 22 | func isIndexExit(client *redisearch.Client) bool { 23 | _, err := client.Info() 24 | if err != nil { 25 | if err.Error() == "NOAUTH Authentication required." { 26 | utils.WriteLog("fatal", "Authentication required") 27 | } 28 | utils.WriteLog("warning", "Index does not exist") 29 | return false 30 | } 31 | return true 32 | } 33 | 34 | func CreateIndex(client *redisearch.Client) { 35 | if isIndexExit(client) { 36 | return 37 | } 38 | 39 | // Create a schema 40 | schema := redisearch.NewSchema(redisearch.DefaultOptions). 41 | AddField(redisearch.NewTextField("output")). 42 | AddField(redisearch.NewTextField("rule")). 43 | AddField(redisearch.NewTextField("priority")). 44 | AddField(redisearch.NewTextField("hostname")). 45 | AddField(redisearch.NewTextField("source")). 46 | AddField(redisearch.NewTextField("tags")). 47 | AddField(redisearch.NewTextField("outputfields")). 48 | AddField(redisearch.NewNumericField("timestamp")). 49 | AddField(redisearch.NewTextField("json")) 50 | 51 | // Drop an existing index. If the index does not exist an error is returned 52 | // client.Drop() 53 | 54 | // Create the index with the given schema 55 | utils.WriteLog("warning", "Create Index") 56 | err := client.CreateIndex(schema) 57 | utils.CheckErr(err) 58 | } 59 | -------------------------------------------------------------------------------- /internal/database/redis/query.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package redis 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | "time" 21 | 22 | "github.com/falcosecurity/falcosidekick-ui/internal/models" 23 | "github.com/falcosecurity/falcosidekick-ui/internal/utils" 24 | ) 25 | 26 | func newQuery(args *models.Arguments) string { 27 | var filter, priority, rule, source, hostname, tags, since string 28 | if args.Filter != "" { 29 | filter = "*" + utils.Escape(args.Filter) + "* " 30 | } 31 | if args.Priority != "" { 32 | p := strings.Split(args.Priority, ",") 33 | for i, j := range p { 34 | p[i] = fmt.Sprintf("@priority:%v", j) 35 | } 36 | priority = fmt.Sprintf("(%v) ", strings.Join(p, " | ")) 37 | } 38 | if args.Rule != "" { 39 | r := strings.Split(args.Rule, ",") 40 | for i, j := range r { 41 | r[i] = fmt.Sprintf("@rule:'%v'", j) 42 | } 43 | rule = fmt.Sprintf("(%v) ", strings.Join(r, " | ")) 44 | } 45 | if args.Source != "" { 46 | r := strings.Split(args.Source, ",") 47 | for i, j := range r { 48 | r[i] = fmt.Sprintf("@source:%v", j) 49 | } 50 | source = fmt.Sprintf("(%v) ", strings.Join(r, " | ")) 51 | } 52 | if args.Hostname != "" { 53 | r := strings.Split(utils.Escape(args.Hostname), ",") 54 | for i, j := range r { 55 | r[i] = fmt.Sprintf("@hostname:%v", j) 56 | } 57 | hostname = fmt.Sprintf("(%v) ", strings.Join(r, " | ")) 58 | } 59 | if args.Tags != "" { 60 | r := strings.Split(utils.Escape(args.Tags), ",") 61 | for i, j := range r { 62 | r[i] = fmt.Sprintf("@tags:%v", j) 63 | } 64 | tags = fmt.Sprintf("(%v) ", strings.Join(r, " | ")) 65 | } 66 | var s int64 67 | if t := int64(utils.ConvertToSeconds(args.Since)); t != 0 { 68 | s = time.Now().UTC().Add(-1 * time.Duration(t) * time.Second).UnixMicro() 69 | } 70 | since = fmt.Sprintf("@timestamp:[%v inf]", s) 71 | 72 | return fmt.Sprintf("%v%v%v%v%v%v%v", filter, priority, rule, source, hostname, tags, since) 73 | } 74 | -------------------------------------------------------------------------------- /internal/database/redis/search.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package redis 16 | 17 | import ( 18 | "encoding/json" 19 | 20 | "github.com/Issif/redisearch-go/redisearch" 21 | "github.com/falcosecurity/falcosidekick-ui/internal/models" 22 | ) 23 | 24 | func SearchKey(client *redisearch.Client, args *models.Arguments) (models.Results, error) { 25 | results, count, err := client.Search(redisearch.NewQuery(newQuery(args)).SetSortBy("timestamp", false).Limit(args.Page*args.Limit, args.Limit)) 26 | if err != nil { 27 | return models.Results{}, err 28 | } 29 | 30 | r := models.Results{} 31 | evts := models.Events{} 32 | for _, i := range results { 33 | var e models.Event 34 | if err := json.Unmarshal([]byte(i.Properties["json"].(string)), &e); err != nil { 35 | return models.Results{}, err 36 | } 37 | evts = append(evts, e) 38 | } 39 | r.Results = evts 40 | r.Stats.Return = int64(len(results)) 41 | r.Stats.All = int64(count) 42 | return r, nil 43 | } 44 | -------------------------------------------------------------------------------- /internal/database/redis/set.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package redis 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "strings" 21 | 22 | "github.com/Issif/redisearch-go/redisearch" 23 | "github.com/falcosecurity/falcosidekick-ui/configuration" 24 | "github.com/falcosecurity/falcosidekick-ui/internal/models" 25 | "github.com/falcosecurity/falcosidekick-ui/internal/utils" 26 | ) 27 | 28 | func SetKey(client *redisearch.Client, event *models.Event) error { 29 | c := configuration.GetConfiguration() 30 | 31 | jsonString, _ := json.Marshal(event) 32 | 33 | of := make([]string, 0, len(event.OutputFields)) 34 | 35 | for _, i := range event.OutputFields { 36 | of = append(of, fmt.Sprintf("%v", i)) 37 | } 38 | 39 | doc := redisearch.NewDocument(fmt.Sprintf("event:%v", event.UUID), 1.0). 40 | Set("rule", event.Rule). 41 | Set("priority", event.Priority). 42 | Set("output", strings.ReplaceAll(utils.Escape(event.Output), ",", "")). 43 | Set("source", event.Source). 44 | Set("timestamp", event.Time.UnixNano()/1e3). 45 | Set("tags", utils.Escape(strings.Join(event.Tags, ","))). 46 | Set("json", string(jsonString)). 47 | Set("outputfields", utils.Escape(strings.Join(of, ","))). 48 | Set("uuid", event.UUID). 49 | SetTTL(c.TTL) 50 | if event.Hostname != "" { 51 | doc.Set("hostname", utils.Escape(event.Hostname)) 52 | } 53 | 54 | err := client.Index([]redisearch.Document{doc}...) 55 | return err 56 | } 57 | -------------------------------------------------------------------------------- /internal/events/add.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package events 16 | 17 | import ( 18 | "fmt" 19 | "net/http" 20 | 21 | "github.com/falcosecurity/falcosidekick-ui/internal/database/redis" 22 | "github.com/falcosecurity/falcosidekick-ui/internal/models" 23 | "github.com/falcosecurity/falcosidekick-ui/internal/utils" 24 | echo "github.com/labstack/echo/v4" 25 | ) 26 | 27 | func Add(e *models.Event) error { 28 | client := redis.GetClient() 29 | if err := redis.SetKey(client, e); err != nil { 30 | utils.WriteLog("error", err.Error()) 31 | return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) 32 | } 33 | 34 | utils.WriteLog("debug", fmt.Sprintf("NEW event 'event:%v'", e.UUID)) 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /internal/events/count.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package events 16 | 17 | import ( 18 | "net/http" 19 | 20 | redis "github.com/falcosecurity/falcosidekick-ui/internal/database/redis" 21 | models "github.com/falcosecurity/falcosidekick-ui/internal/models" 22 | "github.com/falcosecurity/falcosidekick-ui/internal/utils" 23 | echo "github.com/labstack/echo/v4" 24 | ) 25 | 26 | func Count(a *models.Arguments) (models.Results, error) { 27 | c := redis.GetClient() 28 | r, err := redis.CountKey(c, a) 29 | if err != nil { 30 | utils.WriteLog("error", err.Error()) 31 | return models.Results{}, echo.NewHTTPError(http.StatusBadRequest, err.Error()) 32 | } 33 | return r, nil 34 | } 35 | 36 | func CountBy(a *models.Arguments) (models.Results, error) { 37 | c := redis.GetClient() 38 | r, err := redis.CountKeyBy(c, a) 39 | if err != nil { 40 | utils.WriteLog("error", err.Error()) 41 | return models.Results{}, echo.NewHTTPError(http.StatusBadRequest, err.Error()) 42 | } 43 | return r, nil 44 | } 45 | -------------------------------------------------------------------------------- /internal/events/search.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package events 16 | 17 | import ( 18 | "github.com/falcosecurity/falcosidekick-ui/internal/database/redis" 19 | "github.com/falcosecurity/falcosidekick-ui/internal/models" 20 | "github.com/falcosecurity/falcosidekick-ui/internal/utils" 21 | ) 22 | 23 | func Search(a *models.Arguments) (models.Results, error) { 24 | client := redis.GetClient() 25 | results, err := redis.SearchKey(client, a) 26 | if err != nil { 27 | utils.WriteLog("error", err.Error()) 28 | return models.Results{}, err 29 | } 30 | return results, nil 31 | } 32 | -------------------------------------------------------------------------------- /internal/models/models.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package models 16 | 17 | import ( 18 | "encoding/json" 19 | "strconv" 20 | "time" 21 | 22 | echo "github.com/labstack/echo/v4" 23 | ) 24 | 25 | type Events []Event 26 | 27 | type Event struct { 28 | UUID string `json:"uuid" validate:"uuid"` 29 | Output string `json:"output" validate:"required,ascii"` 30 | Priority string `json:"priority" validate:"required,printascii"` 31 | Rule string `json:"rule" validate:"required,printascii"` 32 | Time time.Time `json:"time" validate:"required"` 33 | Source string `json:"source" validate:"printascii"` 34 | OutputFields map[string]interface{} `json:"output_fields"` 35 | Hostname string `json:"hostname" validate:"printascii"` 36 | Tags []string `json:"tags"` 37 | } 38 | 39 | type Payload struct { 40 | Event Event `json:"event" validate:"required"` 41 | Outputs []string `json:"outputs" validate:"required"` 42 | } 43 | 44 | type Aggregation map[string]int64 45 | 46 | type Results struct { 47 | Results interface{} `json:"results,omitempty"` 48 | Stats Statistics `json:"statistics,omitempty"` 49 | } 50 | 51 | // ResultsCount is used for swagger json only 52 | type ResultsCount struct { 53 | Stats Statistics `json:"statistics,omitempty"` 54 | } 55 | 56 | // ResultsCountBy is used for swagger json only 57 | type ResultsCountBy struct { 58 | Results map[string]int64 `json:"results,omitempty"` 59 | Stats Statistics `json:"statistics,omitempty"` 60 | } 61 | 62 | // ResultsSearch is used for swagger json only 63 | type ResultsSearch struct { 64 | Results map[int64]Event `json:"results,omitempty"` 65 | Stats Statistics `json:"statistics,omitempty"` 66 | } 67 | 68 | type Statistics struct { 69 | Return int64 `json:"returned,omitempty"` 70 | Distinct int64 `json:"distincts,omitempty"` 71 | All int64 `json:"all"` 72 | } 73 | 74 | type Arguments struct { 75 | Priority string 76 | Rule string 77 | Since string 78 | Source string 79 | Hostname string 80 | Tags string 81 | Pretty string 82 | Page int 83 | Limit int 84 | Filter string 85 | GroupBy string 86 | } 87 | 88 | func GetArguments(c echo.Context) *Arguments { 89 | limit, _ := strconv.Atoi(c.QueryParam("limit")) 90 | if limit <= 0 { 91 | limit = 100 92 | } 93 | page, _ := strconv.Atoi(c.QueryParam("page")) 94 | page-- 95 | if page < 0 { 96 | page = 0 97 | } 98 | args := &Arguments{ 99 | Priority: emptyNull(c.QueryParam("priority")), 100 | Rule: emptyNull(c.QueryParam("rule")), 101 | Since: emptyNull(c.QueryParam("since")), 102 | Source: emptyNull(c.QueryParam("source")), 103 | Hostname: emptyNull(c.QueryParam("hostname")), 104 | Tags: emptyNull(c.QueryParam("tags")), 105 | Pretty: c.QueryParam("pretty"), 106 | GroupBy: c.Param("groupby"), 107 | Filter: emptyNull(c.QueryParam("filter")), 108 | Page: page, 109 | Limit: limit, 110 | } 111 | return args 112 | } 113 | 114 | func emptyNull(s string) string { 115 | if s == "null" { 116 | return "" 117 | } 118 | return s 119 | } 120 | 121 | func (e Event) String() string { 122 | j, err := json.Marshal(e) 123 | if err != nil { 124 | return "" 125 | } 126 | return string(j) 127 | } 128 | -------------------------------------------------------------------------------- /internal/models/outputs.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package models 16 | 17 | type Outputs []string 18 | 19 | var outputs *Outputs 20 | 21 | func CreateOutputs() *Outputs { 22 | outputs = new(Outputs) 23 | return outputs 24 | } 25 | 26 | func GetOutputs() *Outputs { 27 | return outputs 28 | } 29 | 30 | func (outputs *Outputs) Update(o []string) { 31 | *outputs = o 32 | } 33 | -------------------------------------------------------------------------------- /internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package utils 16 | 17 | import ( 18 | "flag" 19 | "log" 20 | "os" 21 | "regexp" 22 | "sort" 23 | "strconv" 24 | "strings" 25 | 26 | "github.com/falcosecurity/falcosidekick-ui/configuration" 27 | ) 28 | 29 | const ( 30 | extractNumber = "^[0-9]+" 31 | extractUnity = "[a-z-A-Z]+$" 32 | trimPrefix = "(?i)^\\d{2}:\\d{2}:\\d{2}\\.\\d{9}\\:\\ (Debug|Info|Informational|Notice|Warning|Error|Critical|Alert|Emergency)" 33 | ) 34 | 35 | const ( 36 | debugLog = iota 37 | infoLog 38 | warningLog 39 | errorLog 40 | fatalLog 41 | ) 42 | 43 | var regExtractNumber, regExtractUnity, regTrimPrefix *regexp.Regexp 44 | 45 | func init() { 46 | regExtractNumber, _ = regexp.Compile(extractNumber) 47 | regExtractUnity, _ = regexp.Compile(extractUnity) 48 | regTrimPrefix, _ = regexp.Compile(trimPrefix) 49 | } 50 | 51 | func CheckErr(e error) { 52 | if e != nil { 53 | log.Fatalln(e) 54 | } 55 | } 56 | 57 | func WriteLog(level, message string) { 58 | config := configuration.GetConfiguration() 59 | if GetPriortiyInt(level) < GetPriortiyInt(config.LogLevel) { 60 | return 61 | } 62 | var prefix string 63 | switch level { 64 | case "fatal": 65 | prefix = "[ERROR]:" 66 | log.Fatalf("%v %v\n", prefix, message) 67 | case "info": 68 | prefix = "[INFO] :" 69 | case "warning": 70 | prefix = "[WARN] :" 71 | case "error": 72 | prefix = "[ERROR]:" 73 | } 74 | log.Printf("%v %v\n", prefix, message) 75 | } 76 | 77 | func ConvertToSeconds(s string) int { 78 | n, err := strconv.Atoi(regExtractNumber.FindString(s)) 79 | if err != nil { 80 | return 0 81 | } 82 | u := regExtractUnity.FindString(s) 83 | switch u { 84 | case "s", "second", "seconds": 85 | return n 86 | case "m", "min", "minute", "minutes": 87 | return n * 60 88 | case "h", "hour", "hours": 89 | return n * 60 * 60 90 | case "d", "day", "days": 91 | return n * 24 * 60 * 60 92 | case "w", "week", "weeks": 93 | return n * 7 * 24 * 60 * 60 94 | case "M", "month", "months": 95 | return n * 30 * 24 * 60 * 60 96 | case "y", "year", "years": 97 | return n * 365 * 24 * 60 * 60 98 | default: 99 | o, err := strconv.Atoi(s) 100 | if err != nil { 101 | WriteLog("fatal", "invalid TTL") 102 | } 103 | return o 104 | } 105 | } 106 | 107 | func RemoveDuplicate(input []string) []string { 108 | allKeys := make(map[string]bool) 109 | singleKeys := []string{} 110 | for _, i := range input { 111 | if _, value := allKeys[i]; !value { 112 | allKeys[i] = true 113 | singleKeys = append(singleKeys, i) 114 | } 115 | } 116 | sort.Strings(singleKeys) 117 | return singleKeys 118 | } 119 | 120 | func GetStringFlagOrEnvParam(flagString string, envVar string, defaultValue string, usage string) *string { 121 | envvar, present := os.LookupEnv(envVar) 122 | if present { 123 | defaultValue = envvar 124 | } 125 | return flag.String(flagString, defaultValue, usage) 126 | } 127 | 128 | func GetBoolFlagOrEnvParam(flagString string, envVar string, defaultValue bool, usage string) *bool { 129 | envvar, present := os.LookupEnv(envVar) 130 | if present { 131 | if envvar == "true" { 132 | defaultValue = true 133 | } 134 | } 135 | return flag.Bool(flagString, defaultValue, usage) 136 | } 137 | 138 | func GetIntFlagOrEnvParam(flagString string, envVar string, defaultValue int, usage string) *int { 139 | envvar, present := os.LookupEnv(envVar) 140 | if present { 141 | val, err := strconv.Atoi(envvar) 142 | if err == nil { 143 | defaultValue = val 144 | } 145 | } 146 | return flag.Int(flagString, defaultValue, usage) 147 | } 148 | 149 | func GetPriortiyInt(prio string) int { 150 | switch prio { 151 | case "debug": 152 | return debugLog 153 | case "info": 154 | return infoLog 155 | case "warning": 156 | return warningLog 157 | case "error": 158 | return errorLog 159 | case "fatal": 160 | return fatalLog 161 | case "default": 162 | return debugLog 163 | default: 164 | return -1 165 | } 166 | } 167 | 168 | func Escape(s string) string { 169 | s = strings.ReplaceAll(s, ".", `\.`) 170 | // s = strings.ReplaceAll(s, ",", "\\,") 171 | s = strings.ReplaceAll(s, "<", "\\<") 172 | s = strings.ReplaceAll(s, ">", "\\>") 173 | s = strings.ReplaceAll(s, "{", "\\{") 174 | s = strings.ReplaceAll(s, "}", "\\}") 175 | s = strings.ReplaceAll(s, "[", "\\[") 176 | s = strings.ReplaceAll(s, "]", "\\]") 177 | s = strings.ReplaceAll(s, `"`, `\\"`) 178 | s = strings.ReplaceAll(s, "'", "\\'") 179 | s = strings.ReplaceAll(s, ":", "\\:") 180 | s = strings.ReplaceAll(s, ";", "\\;") 181 | s = strings.ReplaceAll(s, "!", "\\!") 182 | s = strings.ReplaceAll(s, "@", "\\@") 183 | s = strings.ReplaceAll(s, "#", "\\#") 184 | s = strings.ReplaceAll(s, "$", "\\$") 185 | s = strings.ReplaceAll(s, "%", "\\%") 186 | s = strings.ReplaceAll(s, "^", "\\^") 187 | s = strings.ReplaceAll(s, "&", "\\&") 188 | s = strings.ReplaceAll(s, "*", "\\*") 189 | s = strings.ReplaceAll(s, "(", "\\(") 190 | s = strings.ReplaceAll(s, ")", "\\)") 191 | s = strings.ReplaceAll(s, "-", "\\-") 192 | s = strings.ReplaceAll(s, "+", "\\+") 193 | s = strings.ReplaceAll(s, "=", "\\=") 194 | s = strings.ReplaceAll(s, "~", "\\~") 195 | return s 196 | } 197 | 198 | func UnEscape(s string) string { 199 | s = strings.ReplaceAll(s, `\.`, ".") 200 | // s = strings.ReplaceAll(s, "\\,", ",") 201 | s = strings.ReplaceAll(s, "\\<", "<") 202 | s = strings.ReplaceAll(s, "\\>", ">") 203 | s = strings.ReplaceAll(s, "\\{", "{") 204 | s = strings.ReplaceAll(s, "\\}", "}") 205 | s = strings.ReplaceAll(s, "\\[", "[") 206 | s = strings.ReplaceAll(s, "\\]", "]") 207 | s = strings.ReplaceAll(s, `\\"`, `"`) 208 | s = strings.ReplaceAll(s, "\\'", "'") 209 | s = strings.ReplaceAll(s, "\\:", ":") 210 | s = strings.ReplaceAll(s, "\\;", ";") 211 | s = strings.ReplaceAll(s, "\\!", "!") 212 | s = strings.ReplaceAll(s, "\\@", "@") 213 | s = strings.ReplaceAll(s, "\\#", "#") 214 | s = strings.ReplaceAll(s, "\\$", "$") 215 | s = strings.ReplaceAll(s, "\\%", "%") 216 | s = strings.ReplaceAll(s, "\\^", "^") 217 | s = strings.ReplaceAll(s, "\\&", "&") 218 | s = strings.ReplaceAll(s, "\\*", "*") 219 | s = strings.ReplaceAll(s, "\\(", "(") 220 | s = strings.ReplaceAll(s, "\\)", ")") 221 | s = strings.ReplaceAll(s, "\\-", "-") 222 | s = strings.ReplaceAll(s, "\\+", "+") 223 | s = strings.ReplaceAll(s, "\\=", "=") 224 | s = strings.ReplaceAll(s, "\\~", "~") 225 | return s 226 | } 227 | 228 | func TrimPrefix(s string) string { 229 | return regTrimPrefix.ReplaceAllString(s, "") 230 | } 231 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | /* 3 | Copyright (C) 2023 The Falco Authors. 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | package main 16 | 17 | import ( 18 | "crypto/subtle" 19 | "flag" 20 | "fmt" 21 | "net" 22 | "net/http" 23 | "os" 24 | "strings" 25 | 26 | "github.com/falcosecurity/falcosidekick-ui/configuration" 27 | "github.com/falcosecurity/falcosidekick-ui/internal/api" 28 | "github.com/falcosecurity/falcosidekick-ui/internal/database/redis" 29 | "github.com/falcosecurity/falcosidekick-ui/internal/models" 30 | "github.com/falcosecurity/falcosidekick-ui/internal/utils" 31 | validator "github.com/go-playground/validator/v10" 32 | echo "github.com/labstack/echo/v4" 33 | "github.com/labstack/echo/v4/middleware" 34 | echoSwagger "github.com/swaggo/echo-swagger" 35 | 36 | _ "github.com/falcosecurity/falcosidekick-ui/docs" 37 | ) 38 | 39 | type CustomValidator struct { 40 | validator *validator.Validate 41 | } 42 | 43 | func init() { 44 | addr := utils.GetStringFlagOrEnvParam("a", "FALCOSIDEKICK_UI_ADDR", "0.0.0.0", "Listen Address") 45 | redisserver := utils.GetStringFlagOrEnvParam("r", "FALCOSIDEKICK_UI_REDIS_URL", "localhost:6379", "Redis server address") 46 | redispassword := utils.GetStringFlagOrEnvParam("w", "FALCOSIDEKICK_UI_REDIS_PASSWORD", "", "Redis server password") 47 | port := utils.GetIntFlagOrEnvParam("p", "FALCOSIDEKICK_UI_PORT", 2802, "Listen Port") 48 | ttl := utils.GetStringFlagOrEnvParam("t", "FALCOSIDEKICK_UI_TTL", "0s", "TTL for keys, the format is X, with unit (s, m, h, d, W, M, y)") 49 | version := flag.Bool("v", false, "Print version") 50 | dev := utils.GetBoolFlagOrEnvParam("x", "FALCOSIDEKICK_UI_DEV", false, "Allow CORS for development") 51 | loglevel := utils.GetStringFlagOrEnvParam("l", "FALCOSIDEKICK_UI_LOGLEVEL", "info", "Log Level") 52 | user := utils.GetStringFlagOrEnvParam("u", "FALCOSIDEKICK_UI_USER", "admin:admin", "User in format :") 53 | disableauth := utils.GetBoolFlagOrEnvParam("d", "FALCOSIDEKICK_UI_DISABLEAUTH", false, "Disable authentication") 54 | 55 | flag.Usage = func() { 56 | help := `Usage of Falcosidekick-UI: 57 | -a string 58 | Listen Address (default "0.0.0.0", environment "FALCOSIDEKICK_UI_ADDR") 59 | -d boolean 60 | Disable authentication (environment "FALCOSIDEKICK_UI_DISABLEAUTH") 61 | -l string 62 | Log level: "debug", "info", "warning", "error" (default "info", environment "FALCOSIDEKICK_UI_LOGLEVEL") 63 | -p int 64 | Listen Port (default "2802", environment "FALCOSIDEKICK_UI_PORT") 65 | -r string 66 | Redis server address (default "localhost:6379", environment "FALCOSIDEKICK_UI_REDIS_URL") 67 | -t string 68 | TTL for keys, the format is X, 69 | with unit (s, m, h, d, W, M, y)" (default "0", environment "FALCOSIDEKICK_UI_TTL") 70 | -u string 71 | User in format : (default "admin:admin", environment "FALCOSIDEKICK_UI_USER") 72 | -v boolean 73 | Display version 74 | -w string 75 | Redis password (default "", environment "FALCOSIDEKICK_UI_REDIS_PASSWORD") 76 | -x boolean 77 | Allow CORS for development (environment "FALCOSIDEKICK_UI_DEV") 78 | ` 79 | fmt.Println(help) 80 | } 81 | 82 | // darkmod := flag.Bool("d", false, "Enable dark mode as default") 83 | flag.Parse() 84 | 85 | if *version { 86 | v := configuration.GetVersionInfo() 87 | fmt.Println(v.String()) 88 | os.Exit(0) 89 | } 90 | 91 | configuration.CreateConfiguration() 92 | config := configuration.GetConfiguration() 93 | if ip := net.ParseIP(*addr); ip == nil { 94 | utils.WriteLog("fatal", "Failed to parse Listen Address") 95 | } 96 | if len(strings.Split(*user, ":")) != 2 { 97 | *user = "admin:admin" 98 | } 99 | config.ListenAddress = *addr 100 | config.ListenPort = *port 101 | config.RedisServer = *redisserver 102 | config.RedisPassword = *redispassword 103 | config.DevMode = *dev 104 | config.TTL = utils.ConvertToSeconds(*ttl) 105 | config.LogLevel = *loglevel 106 | config.Credentials = *user 107 | config.DisableAuth = *disableauth 108 | 109 | if utils.GetPriortiyInt(config.LogLevel) < 0 { 110 | config.LogLevel = "info" 111 | } 112 | 113 | redis.CreateClient() 114 | redis.CreateIndex(redis.GetClient()) 115 | models.CreateOutputs() 116 | } 117 | 118 | // @title Falcosidekick UI 119 | // @version 1.0 120 | // @description Falcosidekick UI 121 | // @contact.name Falco Authors 122 | // @contact.url https://github.com/falcosecurity 123 | // @contact.email cncf-falco-dev@lists.cncf.io 124 | // @license.name Apache 2.0 125 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html 126 | // @accept json 127 | // @produce json 128 | // @schemes http 129 | // @host :2802 130 | // @BasePath /api/v1 131 | func main() { 132 | e := echo.New() 133 | v := &CustomValidator{validator: validator.New()} 134 | config := configuration.GetConfiguration() 135 | 136 | e.Validator = v 137 | e.HideBanner = true 138 | e.HidePort = true 139 | 140 | if config.DevMode { 141 | utils.WriteLog("warning", "DEV mode enabled") 142 | e.Use(middleware.CORS()) 143 | } 144 | if config.DisableAuth { 145 | utils.WriteLog("warning", "Auhentication disabled") 146 | e.Use(middleware.CORS()) 147 | } 148 | 149 | utils.WriteLog("info", fmt.Sprintf("Falcosidekick UI is listening on %v:%v", config.ListenAddress, config.ListenPort)) 150 | utils.WriteLog("info", fmt.Sprintf("Log level is %v", config.LogLevel)) 151 | 152 | e.GET("/docs/*", echoSwagger.WrapHandler) 153 | e.GET("/docs", func(c echo.Context) error { 154 | return c.Redirect(http.StatusPermanentRedirect, "docs/") 155 | }) 156 | e.Static("/*", "frontend/dist").Name = "webui-home" 157 | e.POST("/", api.AddEvent).Name = "add-event" // for compatibility with old Falcosidekicks 158 | 159 | apiRoute := e.Group("/api/v1") 160 | apiRoute.Use(middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{ 161 | Skipper: func(c echo.Context) bool { 162 | if configuration.GetConfiguration().DisableAuth { 163 | return true 164 | } 165 | if c.Request().Method == "POST" { 166 | return true 167 | } 168 | if c.Path() == "/api/v1/healthz" { 169 | return true 170 | } 171 | return false 172 | }, 173 | Validator: func(username, password string, c echo.Context) (bool, error) { 174 | config := configuration.GetConfiguration() 175 | if username == "" || password == "" { 176 | return true, nil 177 | } 178 | if subtle.ConstantTimeCompare([]byte(username+":"+password), []byte(config.Credentials)) == 1 { 179 | return true, nil 180 | } 181 | utils.WriteLog("error", "wrong credentials") 182 | return false, nil 183 | }, 184 | })) 185 | apiRoute.POST("/", api.AddEvent).Name = "add-event" 186 | apiRoute.POST("/auth", api.Authenticate).Name = "authenticate" 187 | apiRoute.POST("/authenticate", api.Authenticate).Name = "authenticate" 188 | apiRoute.GET("/config", api.GetConfiguration).Name = "get-configuration" 189 | apiRoute.GET("/configuration", api.GetConfiguration).Name = "get-configuration" 190 | apiRoute.GET("/version", api.GetVersionInfo).Name = "get-version" 191 | apiRoute.GET("/healthz", api.Healthz).Name = "healthz" 192 | apiRoute.GET("/outputs", api.GetOutputs).Name = "list-outputs" 193 | 194 | eventsRoute := apiRoute.Group("/events") 195 | eventsRoute.POST("/add", api.AddEvent).Name = "add-event" 196 | eventsRoute.GET("/count", api.CountEvent).Name = "count-events" 197 | eventsRoute.GET("/count/:groupby", api.CountByEvent).Name = "count-events-by" 198 | eventsRoute.GET("/search", api.Search).Name = "search-keys" 199 | 200 | e.Logger.Fatal(e.Start(fmt.Sprintf("%v:%v", config.ListenAddress, config.ListenPort))) 201 | } 202 | 203 | func (cv *CustomValidator) Validate(i interface{}) error { 204 | if err := cv.validator.Struct(i); err != nil { 205 | utils.WriteLog("error", err.Error()) 206 | return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) 207 | } 208 | return nil 209 | } 210 | -------------------------------------------------------------------------------- /release/ldflags.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | # Copyright (C) 2023 The Falco Authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 12 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations under the License. 14 | # 15 | 16 | set -o errexit 17 | set -o nounset 18 | set -o pipefail 19 | 20 | # Output LDFlAGS for a given environment. LDFLAGS are applied to all go binary 21 | # builds. 22 | # 23 | # Args: env 24 | function ldflags() { 25 | local GIT_VERSION=$(git describe --tags --always --dirty) 26 | local GIT_COMMIT=$(git rev-parse HEAD) 27 | 28 | local GIT_TREESTATE="clean" 29 | if [[ $(git diff --stat) != '' ]]; then 30 | GIT_TREESTATE="dirty" 31 | fi 32 | 33 | local DATE_FMT="+%Y-%m-%dT%H:%M:%SZ" 34 | local BUILD_DATE=$(date "$DATE_FMT") 35 | local SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) 36 | if [ $SOURCE_DATE_EPOCH ] 37 | then 38 | local BUILD_DATE=$(date -u -d "@$SOURCE_DATE_EPOCH" "$DATE_FMT" 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" "$DATE_FMT" 2>/dev/null || date -u "$DATE_FMT") 39 | fi 40 | 41 | echo "-buildid= -X main.GitVersion=${GIT_VERSION} \ 42 | -X main.gitCommit=${GIT_COMMIT} \ 43 | -X main.gitTreeState=${GIT_TREESTATE} \ 44 | -X main.buildDate=${BUILD_DATE}" 45 | } -------------------------------------------------------------------------------- /tools/go_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: Apache-2.0 3 | # 4 | # Copyright (C) 2023 The Falco Authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 12 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 13 | # specific language governing permissions and limitations under the License. 14 | # 15 | 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | if [ -z "${1}" ]; then 22 | echo "must provide module as first parameter" 23 | exit 1 24 | fi 25 | 26 | if [ -z "${2}" ]; then 27 | echo "must provide binary name as second parameter" 28 | exit 1 29 | fi 30 | 31 | if [ -z "${3}" ]; then 32 | echo "must provide version as third parameter" 33 | exit 1 34 | fi 35 | 36 | if [ -z "${GOBIN}" ]; then 37 | echo "GOBIN is not set. Must set GOBIN to install the bin in a specified directory." 38 | exit 1 39 | fi 40 | 41 | tmp_dir=$(mktemp -d -t goinstall_XXXXXXXXXX) 42 | function clean { 43 | rm -rf "${tmp_dir}" 44 | } 45 | trap clean EXIT 46 | 47 | rm "${GOBIN}/${2}"* || true 48 | 49 | cd "${tmp_dir}" 50 | 51 | # create a new module in the tmp directory 52 | go mod init fake/mod 53 | 54 | # install the golang module specified as the first argument 55 | go install -tags tools "${1}@${3}" 56 | mv "${GOBIN}/${2}" "${GOBIN}/${2}-${3}" 57 | ln -sf "${GOBIN}/${2}-${3}" "${GOBIN}/${2}" 58 | --------------------------------------------------------------------------------