├── .github ├── CODEOWNERS ├── dependabot.yaml └── workflows │ ├── chart-lint.yml │ ├── chart-release.yml │ ├── ci.yml │ └── docker.yaml ├── .gitignore ├── .licensei.toml ├── .vscode └── launch.json ├── Dockerfile ├── Dockerfile-analyzer ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── dast_types.go │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── charts └── dast-operator │ ├── .gitignore │ ├── .helmignore │ ├── Chart.yaml │ ├── ci │ └── test-values.yaml │ ├── crds │ └── security.banzaicloud.io_dasts.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── certificate.yaml │ ├── deployment.yaml │ ├── rbac.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ ├── tests │ │ └── test-connection.yaml │ └── validating-webhook.yaml │ └── values.yaml ├── cmd └── dynamic-analyzer │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── root.go │ ├── scanner.go │ └── server.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ └── security.banzaicloud.io_dasts.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_dasts.yaml │ │ └── webhook_in_dasts.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── dast_editor_role.yaml │ ├── dast_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ └── role_binding.yaml ├── samples │ ├── security_v1alpha1_dast.yaml │ ├── security_v1alpha1_dast_external.yaml │ ├── security_v1alpha1_dast_extraconfig.yaml │ ├── test-api.yaml │ ├── test_ingress.yaml │ ├── test_service.yaml │ └── test_v1_ingress.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── controllers ├── dast_controller.go ├── service_controller.go └── suite_test.go ├── docs └── images │ └── dast.png ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt └── check-state.sh ├── main.go ├── pkg ├── k8sutil │ ├── deployment.go │ ├── resource.go │ ├── secret.go │ └── service.go └── resources │ ├── analyzer │ ├── analyzer.go │ └── job.go │ ├── reconciler.go │ └── zaproxy │ ├── deployment.go │ ├── secret.go │ ├── service.go │ └── zaproxy.go └── webhooks └── ingress_validator.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @pbalogh-sa 2 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Keep Docker dependencies up to date 4 | - package-ecosystem: "docker" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" -------------------------------------------------------------------------------- /.github/workflows/chart-lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint and Test Charts 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | paths: 7 | - 'charts/**' 8 | - '.github/workflows/chart-lint.yml' 9 | 10 | 11 | 12 | jobs: 13 | 14 | lint-test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Fetch history 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | run: git fetch --prune --unshallow 24 | 25 | - name: Run chart-testing (lint) 26 | id: lint 27 | uses: helm/chart-testing-action@v1.0.0 28 | with: 29 | command: lint 30 | 31 | # - name: Set up Go 1.16 32 | # uses: actions/setup-go@v1 33 | # with: 34 | # go-version: 1.16 35 | # if: steps.lint.outputs.changed == 'true' 36 | 37 | # - name: Build dast-operator Docker image 38 | # run: | 39 | # make docker-build 40 | # if: steps.lint.outputs.changed == 'true' 41 | 42 | # - name: Create kind cluster 43 | # uses: helm/kind-action@v1.1.0 44 | # with: 45 | # cluster_name: "chart-testing" 46 | # # Only build a kind cluster if there are chart changes to test. 47 | # if: steps.lint.outputs.changed == 'true' 48 | 49 | # - name: Load Docker images to kind 50 | # run: | 51 | # kind load docker-image --name chart-testing banzaicloud/dast-operator:latest 52 | # if: steps.lint.outputs.changed == 'true' 53 | 54 | # - name: Deploying cert-manager 55 | # run: | 56 | # kubectl get pod -A 57 | # kubectl create namespace cert-manager 58 | # helm repo add jetstack https://charts.jetstack.io 59 | # helm repo update 60 | # kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.0.4/cert-manager.crds.yaml 61 | # helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.0.4 62 | # timeout 100s hack/check-state.sh 63 | # if: steps.lint.outputs.changed == 'true' 64 | 65 | # - name: Run chart-testing (install) 66 | # uses: helm/chart-testing-action@v1.0.0 67 | # with: 68 | # command: install 69 | # if: steps.lint.outputs.changed == 'true' 70 | -------------------------------------------------------------------------------- /.github/workflows/chart-release.yml: -------------------------------------------------------------------------------- 1 | name: Create and upload chart 2 | on: 3 | push: 4 | tags: 5 | - "charts/[0-9]+.[0-9]+.[0-9]+" 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | - uses: unwrittenmedia/helm3-push-action@v1.0.2 13 | env: 14 | SOURCE_DIR: 'charts' 15 | CHART_FOLDER: 'dast-operator' 16 | CHARTMUSEUM_URL: 'https://kubernetes-charts.banzaicloud.com' 17 | CHARTMUSEUM_USER: '${{ secrets.HELM_REPO_USERNAME }}' 18 | CHARTMUSEUM_PASSWORD: '${{ secrets.HELM_REPO_PASSWORD }}' 19 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: [master] 5 | paths-ignore: 6 | - "README.md" 7 | - "charts/**" 8 | - ".github/workflows/chart-lint.yml" 9 | - ".github/workflows/chart-release.yml" 10 | - ".github/workflows/docker.yml" 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Set up Go 1.16 19 | uses: actions/setup-go@v1 20 | with: 21 | go-version: 1.16 22 | 23 | - name: Check out code 24 | uses: actions/checkout@v1 25 | 26 | - name: Buld code 27 | run: make 28 | 29 | acceptance-test: 30 | name: Acceptance test 31 | runs-on: ubuntu-latest 32 | env: 33 | DOCKER_LATEST: 1 34 | VERSION: latest 35 | 36 | steps: 37 | - name: Set up Go 1.16 38 | uses: actions/setup-go@v1 39 | with: 40 | go-version: 1.16 41 | 42 | - name: Checkout code 43 | uses: actions/checkout@v2 44 | 45 | - name: Build dast-operator Docker image 46 | run: | 47 | make docker-build 48 | 49 | - name: Build dast-analyzer Docker image 50 | run: | 51 | make docker-analyzer 52 | 53 | - name: Create k8s Kind Cluster 54 | uses: helm/kind-action@v1.2.0 55 | with: 56 | cluster_name: "acceptance-test" 57 | 58 | - name: Load Docker images to kind 59 | run: | 60 | kind load docker-image --name acceptance-test banzaicloud/dast-operator:latest 61 | kind load docker-image --name acceptance-test banzaicloud/dast-analyzer:latest 62 | 63 | - name: Deploying cert-manager 64 | run: | 65 | kubectl create namespace cert-manager 66 | helm repo add jetstack https://charts.jetstack.io 67 | helm repo update 68 | kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.crds.yaml 69 | helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.1.0 70 | 71 | - name: Check cert-manager webhook 72 | run: | 73 | timeout 100s hack/check-state.sh 74 | 75 | - name: Deploying dast-operator 76 | run: | 77 | kubectl create ns dast-operator-system 78 | make deploy 79 | 80 | - name: Deploy dast resource 81 | run: | 82 | kubectl create ns zaproxy 83 | kubectl apply -f config/samples/security_v1alpha1_dast.yaml -n zaproxy 84 | 85 | - name: Deploy test applications 86 | run: | 87 | kubectl create ns test 88 | kubectl apply -f config/samples/test_service.yaml -n test 89 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - "[0-9]+.[0-9]+.[0-9]+" 8 | 9 | env: 10 | PLATFORMS: linux/amd64,linux/arm64 11 | jobs: 12 | docker: 13 | name: Docker 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up QEMU 20 | uses: docker/setup-qemu-action@v1 21 | - name: Set up Docker Buildx 22 | uses: docker/setup-buildx-action@v1 23 | - name: Cache Docker layers 24 | uses: actions/cache@v2 25 | with: 26 | path: /tmp/.buildx-cache 27 | key: ${{ runner.os }}-buildx-${{ github.ref }} 28 | restore-keys: | 29 | ${{ runner.os }}-buildx- 30 | 31 | - name: Determine tag or commit 32 | uses: haya14busa/action-cond@v1 33 | id: refortag 34 | with: 35 | cond: ${{ startsWith(github.ref, 'refs/tags/') }} 36 | if_true: ${{ github.ref }} 37 | if_false: latest 38 | - name: Determine image tag 39 | id: imagetag 40 | run: echo "value=${TAG_OR_BRANCH##*/}" >> $GITHUB_OUTPUT 41 | env: 42 | TAG_OR_BRANCH: ${{ steps.refortag.outputs.value }} 43 | 44 | - name: Login to GitHub Container Registry 45 | uses: docker/login-action@v1 46 | with: 47 | registry: ghcr.io 48 | username: ${{ github.repository_owner }} 49 | password: ${{ secrets.CR_PAT }} 50 | if: ${{ github.event_name == 'push' }} 51 | 52 | - name: Build dast-operator 53 | uses: docker/build-push-action@v2 54 | with: 55 | tags: ghcr.io/banzaicloud/dast-operator:${{ steps.imagetag.outputs.value }} 56 | file: Dockerfile 57 | platforms: ${{ env.PLATFORMS }} 58 | push: ${{ github.event_name == 'push' }} 59 | cache-from: type=local,src=/tmp/.buildx-cache 60 | cache-to: type=local,dest=/tmp/.buildx-cache 61 | labels: | 62 | org.opencontainers.image.title=${{ github.event.repository.name }} 63 | org.opencontainers.image.description=${{ github.event.repository.description }} 64 | org.opencontainers.image.url=${{ github.event.repository.html_url }} 65 | org.opencontainers.image.source=${{ github.event.repository.clone_url }} 66 | org.opencontainers.image.version=${{ steps.tags.outputs.version }} 67 | org.opencontainers.image.created=${{ steps.tags.outputs.build_date }} 68 | org.opencontainers.image.revision=${{ github.sha }} 69 | org.opencontainers.image.licenses=${{ github.event.repository.license.spdx_id }} 70 | org.opencontainers.image.vendor=Banzai Cloud 71 | - name: Build dast-analyzer 72 | uses: docker/build-push-action@v2 73 | with: 74 | tags: ghcr.io/banzaicloud/dast-analyzer:${{ steps.imagetag.outputs.value }} 75 | file: Dockerfile-analyzer 76 | platforms: ${{ env.PLATFORMS }} 77 | push: ${{ github.event_name == 'push' }} 78 | cache-from: type=local,src=/tmp/.buildx-cache 79 | cache-to: type=local,dest=/tmp/.buildx-cache 80 | labels: | 81 | org.opencontainers.image.title=dast-analyzer 82 | org.opencontainers.image.description=${{ github.event.repository.description }} 83 | org.opencontainers.image.url=${{ github.event.repository.html_url }} 84 | org.opencontainers.image.source=${{ github.event.repository.clone_url }} 85 | org.opencontainers.image.version=${{ steps.tags.outputs.version }} 86 | org.opencontainers.image.created=${{ steps.tags.outputs.build_date }} 87 | org.opencontainers.image.revision=${{ github.sha }} 88 | org.opencontainers.image.licenses=${{ github.event.repository.license.spdx_id }} 89 | org.opencontainers.image.vendor=Banzai Cloud 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Kubernetes Generated files - skip generated files, except for vendored files 17 | 18 | !vendor/**/zz_generated.* 19 | 20 | # editor and IDE paraphernalia 21 | .idea 22 | *.swp 23 | *.swo 24 | *~ 25 | -------------------------------------------------------------------------------- /.licensei.toml: -------------------------------------------------------------------------------- 1 | approved = [ 2 | "mit", 3 | "apache-2.0", 4 | "bsd-3-clause", 5 | "bsd-2-clause", 6 | "mpl-2.0", 7 | ] 8 | 9 | ignored = [ 10 | 11 | "sigs.k8s.io/yaml", # Forked from above 12 | "gopkg.in/fsnotify.v1", # bsd-3-clause 13 | "github.com/gogo/protobuf", # BSD3 14 | "github.com/davecgh/go-spew", # ISC license 15 | 16 | ] 17 | 18 | [header] 19 | ignoreFiles = [] 20 | 21 | template = """/* 22 | Copyright 2019 Banzai Cloud. 23 | 24 | Licensed under the Apache License, Version 2.0 (the "License"); 25 | you may not use this file except in compliance with the License. 26 | You may obtain a copy of the License at 27 | 28 | http://www.apache.org/licenses/LICENSE-2.0 29 | 30 | Unless required by applicable law or agreed to in writing, software 31 | distributed under the License is distributed on an "AS IS" BASIS, 32 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | See the License for the specific language governing permissions and 34 | limitations under the License. 35 | */ 36 | """ 37 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceRoot}/main.go", 13 | "env": {}, 14 | "args": [] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.5-alpine AS builder 2 | 3 | WORKDIR /workspace 4 | # Copy the go source 5 | COPY main.go main.go 6 | COPY api/ api/ 7 | COPY controllers/ controllers/ 8 | COPY webhooks/ webhooks/ 9 | COPY pkg/ pkg/ 10 | # Copy the Go Modules manifests 11 | COPY go.mod go.mod 12 | COPY go.sum go.sum 13 | 14 | # Build 15 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go 16 | 17 | # Use distroless as minimal base image to package the manager binary 18 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 19 | FROM alpine:3.17.1 20 | WORKDIR / 21 | COPY --from=builder /workspace/manager . 22 | USER 65534:65534 23 | 24 | ENTRYPOINT ["/manager"] 25 | -------------------------------------------------------------------------------- /Dockerfile-analyzer: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.5-alpine AS builder 2 | 3 | WORKDIR /workspace 4 | # Copy the go source and go modules manifests 5 | COPY cmd/dynamic-analyzer . 6 | 7 | # Build 8 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o dynamic-analyzer ./... 9 | 10 | FROM alpine:3.17.1 11 | WORKDIR / 12 | COPY --from=builder /workspace/dynamic-analyzer . 13 | ENTRYPOINT ["/dynamic-analyzer"] 14 | -------------------------------------------------------------------------------- /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 | 2 | # Image URL to use all building/pushing image targets 3 | ORGANIZATION = banzaicloud 4 | IMG ?= ${ORGANIZATION}/dast-operator:latest 5 | ANALYZER_IMG ?= ${ORGANIZATION}/dast-analyzer:latest 6 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 7 | CRD_OPTIONS ?= "crd:trivialVersions=true" 8 | LICENSEI_VERSION = 0.3.1 9 | 10 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 11 | ifeq (,$(shell go env GOBIN)) 12 | GOBIN=$(shell go env GOPATH)/bin 13 | else 14 | GOBIN=$(shell go env GOBIN) 15 | endif 16 | 17 | all: manager 18 | 19 | # Run tests 20 | test: generate fmt vet manifests license-check 21 | go test ./... -coverprofile cover.out 22 | 23 | # Build manager binary 24 | manager: generate fmt vet 25 | go build -o bin/manager main.go 26 | 27 | analyzer: 28 | cd cmd/dynamic-analyzer; go build -o ../../bin/dynamic-analyzer ./... ;cd ../.. 29 | 30 | # Run against the configured Kubernetes cluster in ~/.kube/config 31 | run: generate fmt vet manifests 32 | go run ./main.go 33 | 34 | # Install CRDs into a cluster 35 | install: manifests 36 | kustomize build config/crd | kubectl apply -f - 37 | 38 | # Uninstall CRDs from a cluster 39 | uninstall: manifests 40 | kustomize build config/crd | kubectl delete -f - 41 | 42 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 43 | deploy: 44 | cd config/manager && kustomize edit set image controller=${IMG} 45 | kustomize build config/default | kubectl apply --validate=false -f - 46 | 47 | # Generate manifests e.g. CRD, RBAC etc. 48 | manifests: controller-gen 49 | $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases 50 | 51 | # Run go fmt against code 52 | fmt: 53 | go fmt ./... 54 | 55 | # Run go vet against code 56 | vet: 57 | go vet ./... 58 | 59 | # Generate code 60 | generate: controller-gen 61 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 62 | 63 | # Build the docker image 64 | docker-build: 65 | docker build . -t ${IMG} 66 | 67 | docker-analyzer: 68 | docker build . -t ${ANALYZER_IMG} -f Dockerfile-analyzer 69 | 70 | # Push the docker image 71 | docker-push: 72 | docker push ${IMG} 73 | 74 | # find or download controller-gen 75 | # download controller-gen if necessary 76 | controller-gen: 77 | ifeq (, $(shell which controller-gen)) 78 | @{ \ 79 | set -e ;\ 80 | CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ 81 | cd $$CONTROLLER_GEN_TMP_DIR ;\ 82 | go mod init tmp ;\ 83 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 ;\ 84 | rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ 85 | } 86 | CONTROLLER_GEN=$(GOBIN)/controller-gen 87 | else 88 | CONTROLLER_GEN=$(shell which controller-gen) 89 | endif 90 | 91 | 92 | bin/licensei: bin/licensei-${LICENSEI_VERSION} 93 | @ln -sf licensei-${LICENSEI_VERSION} bin/licensei 94 | bin/licensei-${LICENSEI_VERSION}: 95 | @mkdir -p bin 96 | curl -sfL https://git.io/licensei | bash -s v${LICENSEI_VERSION} 97 | @mv bin/licensei $@ 98 | 99 | .PHONY: license-check 100 | license-check: bin/licensei 101 | bin/licensei check 102 | bin/licensei header 103 | 104 | 105 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: banzaicloud.io 2 | repo: github.com/banzaicloud/dast-operator 3 | resources: 4 | - group: security 5 | kind: Dast 6 | version: v1alpha1 7 | version: "2" 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DAST operator 2 | 3 | > Dynamic application security testing (DAST) is a process of testing an application or software in an operating state. 4 | 5 | This operator leverages [OWASP ZAP](https://www.zaproxy.org) to make automated security testing for web applications and APIs based on OpenAPI definitions. 6 | 7 | ### The operator current features: 8 | - Deploy OWASP ZAP proxy defined in custom resource 9 | - Scan external URL defined in custom resource 10 | - Scan internal services based on its annotations 11 | - API Security testing based on OpenAPI definition 12 | - Before deploying ingress, check backend services whether scanned and scan results are below defined thresholds 13 | 14 | ### On the DAST operator roadmap: 15 | - In webhook, check the scanner job is running, completed or not exist 16 | - Improve service status check 17 | - Handle multiple service ports 18 | - Handle different service protocols 19 | - Use HTTPS instead of HTTP connecting to ZAP 20 | - Generate randomly ZAP API key if not defined 21 | - API testing with JMeter and ZAP 22 | - Parameterized security payload with fuzz 23 | - Automated SQLi testing using SQLmap 24 | 25 | ## Structure of the DAST operator: 26 | DAST operator running two reconcilers and one [validating admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook) 27 | 28 | ![DAST OPERATOR](docs/images/dast.png) 29 | 30 | ### Reconcilers 31 | - DAST reconciler 32 | - Service reconciler 33 | 34 | ### Webhook 35 | - Validating webhook for ingress 36 | 37 | ## Current limitations: 38 | Using the webhook feature, deploying an ingress is only successful when the backend service has been already scanned. If we deploy something with Helm that contains a service and an ingress definition as well, the ingress deployment will fail as to the scan progress of the backend service is not finished yet. 39 | 40 | ## Deploy the cert-manager 41 | 42 | First of all we need to deploy `cert-manager` 43 | ```shell 44 | kubectl create namespace cert-manager 45 | helm repo add jetstack https://charts.jetstack.io 46 | helm repo update 47 | kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.0.4/cert-manager.crds.yaml 48 | helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.0.4 49 | ``` 50 | 51 | You can read more about the installation of the cert-manager in the [official documentation](https://cert-manager.io/docs/installation/kubernetes/) 52 | 53 | ## Deploy the dast-operator via helm 54 | 55 | Or you can install via helm: 56 | ```shell 57 | helm repo add banzaicloud https://kubernetes-charts.banzaicloud.com/ 58 | helm install dast-operator banzaicloud/dast-operator 59 | ``` 60 | 61 | ## Build images and deploy the operator manually 62 | 63 | ```shell 64 | git clone https://github.com/banzaicloud/dast-operator.git 65 | cd dast-operator 66 | make docker-build 67 | make docker-analyzer 68 | ``` 69 | 70 | If you're using `Kind` cluster for testing, you will have to load images to it. 71 | ```shell 72 | kind load docker-image banzaicloud/dast-operator:latest 73 | kind load docker-image banzaicloud/dast-analyzer:latest 74 | ``` 75 | 76 | Clone dast-operator 77 | ```shell 78 | git clone https://github.com/banzaicloud/dast-operator.git 79 | cd dast-operator 80 | ``` 81 | 82 | Deploy dast-operator 83 | ```shell 84 | make deploy 85 | ``` 86 | 87 | ## Examples 88 | 89 | ### Deploy OWASP ZAP 90 | Deploy example CR 91 | ```shell 92 | kubectl create ns zaproxy 93 | kubectl apply -f https://raw.githubusercontent.com/banzaicloud/dast-operator/master/config/samples/security_v1alpha1_dast.yaml -n zaproxy 94 | ``` 95 | 96 | Content of Dast custom resource: 97 | ```yaml 98 | apiVersion: security.banzaicloud.io/v1alpha1 99 | kind: Dast 100 | metadata: 101 | name: dast-sample 102 | spec: 103 | zaproxy: 104 | name: dast-test 105 | apikey: abcd1234 106 | ``` 107 | 108 | ### Deploy the application and initiate active scan 109 | ```shell 110 | kubectl create ns test 111 | kubectl apply -f https://raw.githubusercontent.com/banzaicloud/dast-operator/master/config/samples/test_service.yaml -n test 112 | ``` 113 | 114 | Content of `test_secvice.yaml`: 115 | ```yaml 116 | apiVersion: apps/v1 117 | kind: Deployment 118 | metadata: 119 | name: test-deployment 120 | labels: 121 | app: nginx 122 | spec: 123 | replicas: 1 124 | selector: 125 | matchLabels: 126 | app: nginx 127 | secscan: dast 128 | template: 129 | metadata: 130 | labels: 131 | app: nginx 132 | secscan: dast 133 | spec: 134 | containers: 135 | - name: nginx 136 | image: nginx:1.16.0-alpine 137 | ports: 138 | - containerPort: 80 139 | --- 140 | apiVersion: v1 141 | kind: Service 142 | metadata: 143 | name: test-service 144 | annotations: 145 | dast.security.banzaicloud.io/zaproxy: "dast-test" 146 | dast.security.banzaicloud.io/zaproxy-namespace: "zaproxy" 147 | spec: 148 | selector: 149 | app: nginx 150 | secscan: dast 151 | ports: 152 | - port: 80 153 | targetPort: 80 154 | ``` 155 | 156 | ### Test the validating webhook 157 | 158 | Deploy ingress with previously defined `test-service` backend. 159 | ```shell 160 | kubectl apply -f https://raw.githubusercontent.com/banzaicloud/dast-operator/master/config/samples/test_ingress.yaml -n test 161 | ``` 162 | 163 | Example ingress definition: 164 | ```yaml 165 | apiVersion: extensions/v1beta1 166 | kind: Ingress 167 | metadata: 168 | name: test-ingress 169 | annotations: 170 | dast.security.banzaicloud.io/medium: "2" 171 | dast.security.banzaicloud.io/low: "5" 172 | dast.security.banzaicloud.io/informational: "10" 173 | nginx.ingress.kubernetes.io/rewrite-target: / 174 | spec: 175 | rules: 176 | - http: 177 | paths: 178 | - path: / 179 | backend: 180 | serviceName: test-service 181 | servicePort: 80 182 | ``` 183 | 184 | 185 | ### Scan external URL 186 | ```shell 187 | kubectl create ns external 188 | kubectl apply -f https://raw.githubusercontent.com/banzaicloud/dast-operator/master/config/samples/security_v1alpha1_dast_external.yaml -n external 189 | ``` 190 | 191 | Content of DAST CR 192 | ```yaml 193 | apiVersion: security.banzaicloud.io/v1alpha1 194 | kind: Dast 195 | metadata: 196 | name: dast-sample-external 197 | spec: 198 | zaproxy: 199 | name: dast-test-external 200 | apikey: abcd1234 201 | analyzer: 202 | image: banzaicloud/dast-analyzer:latest 203 | name: external-test 204 | target: http://example.com 205 | ``` 206 | 207 | 208 | ### Define OpenAPI definition as annotation in a service 209 | ```yaml 210 | apiVersion: v1 211 | kind: Service 212 | metadata: 213 | name: test-api-service 214 | annotations: 215 | dast.security.banzaicloud.io/zaproxy: "dast-test" 216 | dast.security.banzaicloud.io/zaproxy-namespace: "zaproxy" 217 | dast.security.banzaicloud.io/apiscan: "true" 218 | dast.security.banzaicloud.io/openapi-url: "https://raw.githubusercontent.com/sagikazarmark/modern-go-application/master/api/openapi/todo/openapi.yaml" 219 | spec: 220 | selector: 221 | app: mga 222 | secscan: dast 223 | ports: 224 | - port: 8000 225 | targetPort: 8000 226 | ``` 227 | -------------------------------------------------------------------------------- /api/v1alpha1/dast_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 25 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 26 | 27 | // DastSpec defines the desired state of Dast 28 | type DastSpec struct { 29 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 30 | // Important: Run "make" to regenerate code after modifying this file 31 | ZaProxy ZaProxy `json:"zaproxy"` 32 | Analyzer Analyzer `json:"analyzer,omitempty"` 33 | } 34 | 35 | type ZaProxy struct { 36 | Image string `json:"image,omitempty"` 37 | Name string `json:"name"` 38 | NameSpace string `json:"namespace,omitempty"` 39 | APIKey string `json:"apikey,omitempty"` 40 | Config []string `json:"config,omitempty"` 41 | } 42 | 43 | type Analyzer struct { 44 | Image string `json:"image"` 45 | Name string `json:"name"` 46 | Target string `json:"target,omitempty"` 47 | Service *corev1.Service `json:"service,omitempty"` 48 | } 49 | 50 | // DastStatus defines the observed state of Dast 51 | type DastStatus struct { 52 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 53 | // Important: Run "make" to regenerate code after modifying this file 54 | } 55 | 56 | // +kubebuilder:object:root=true 57 | 58 | // Dast is the Schema for the dasts API 59 | type Dast struct { 60 | metav1.TypeMeta `json:",inline"` 61 | metav1.ObjectMeta `json:"metadata,omitempty"` 62 | 63 | Spec DastSpec `json:"spec"` 64 | Status DastStatus `json:"status,omitempty"` 65 | } 66 | 67 | // +kubebuilder:object:root=true 68 | 69 | // DastList contains a list of Dast 70 | type DastList struct { 71 | metav1.TypeMeta `json:",inline"` 72 | metav1.ListMeta `json:"metadata,omitempty"` 73 | Items []Dast `json:"items"` 74 | } 75 | 76 | func init() { 77 | SchemeBuilder.Register(&Dast{}, &DastList{}) 78 | } 79 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the security v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=security.banzaicloud.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "security.banzaicloud.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | /* 5 | Copyright 2019 Banzai Cloud. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1alpha1 23 | 24 | import ( 25 | "k8s.io/api/core/v1" 26 | runtime "k8s.io/apimachinery/pkg/runtime" 27 | ) 28 | 29 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 30 | func (in *Analyzer) DeepCopyInto(out *Analyzer) { 31 | *out = *in 32 | if in.Service != nil { 33 | in, out := &in.Service, &out.Service 34 | *out = new(v1.Service) 35 | (*in).DeepCopyInto(*out) 36 | } 37 | } 38 | 39 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Analyzer. 40 | func (in *Analyzer) DeepCopy() *Analyzer { 41 | if in == nil { 42 | return nil 43 | } 44 | out := new(Analyzer) 45 | in.DeepCopyInto(out) 46 | return out 47 | } 48 | 49 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 50 | func (in *Dast) DeepCopyInto(out *Dast) { 51 | *out = *in 52 | out.TypeMeta = in.TypeMeta 53 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 54 | in.Spec.DeepCopyInto(&out.Spec) 55 | out.Status = in.Status 56 | } 57 | 58 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Dast. 59 | func (in *Dast) DeepCopy() *Dast { 60 | if in == nil { 61 | return nil 62 | } 63 | out := new(Dast) 64 | in.DeepCopyInto(out) 65 | return out 66 | } 67 | 68 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 69 | func (in *Dast) DeepCopyObject() runtime.Object { 70 | if c := in.DeepCopy(); c != nil { 71 | return c 72 | } 73 | return nil 74 | } 75 | 76 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 77 | func (in *DastList) DeepCopyInto(out *DastList) { 78 | *out = *in 79 | out.TypeMeta = in.TypeMeta 80 | in.ListMeta.DeepCopyInto(&out.ListMeta) 81 | if in.Items != nil { 82 | in, out := &in.Items, &out.Items 83 | *out = make([]Dast, len(*in)) 84 | for i := range *in { 85 | (*in)[i].DeepCopyInto(&(*out)[i]) 86 | } 87 | } 88 | } 89 | 90 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DastList. 91 | func (in *DastList) DeepCopy() *DastList { 92 | if in == nil { 93 | return nil 94 | } 95 | out := new(DastList) 96 | in.DeepCopyInto(out) 97 | return out 98 | } 99 | 100 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 101 | func (in *DastList) DeepCopyObject() runtime.Object { 102 | if c := in.DeepCopy(); c != nil { 103 | return c 104 | } 105 | return nil 106 | } 107 | 108 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 109 | func (in *DastSpec) DeepCopyInto(out *DastSpec) { 110 | *out = *in 111 | in.ZaProxy.DeepCopyInto(&out.ZaProxy) 112 | in.Analyzer.DeepCopyInto(&out.Analyzer) 113 | } 114 | 115 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DastSpec. 116 | func (in *DastSpec) DeepCopy() *DastSpec { 117 | if in == nil { 118 | return nil 119 | } 120 | out := new(DastSpec) 121 | in.DeepCopyInto(out) 122 | return out 123 | } 124 | 125 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 126 | func (in *DastStatus) DeepCopyInto(out *DastStatus) { 127 | *out = *in 128 | } 129 | 130 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DastStatus. 131 | func (in *DastStatus) DeepCopy() *DastStatus { 132 | if in == nil { 133 | return nil 134 | } 135 | out := new(DastStatus) 136 | in.DeepCopyInto(out) 137 | return out 138 | } 139 | 140 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 141 | func (in *ZaProxy) DeepCopyInto(out *ZaProxy) { 142 | *out = *in 143 | if in.Config != nil { 144 | in, out := &in.Config, &out.Config 145 | *out = make([]string, len(*in)) 146 | copy(*out, *in) 147 | } 148 | } 149 | 150 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ZaProxy. 151 | func (in *ZaProxy) DeepCopy() *ZaProxy { 152 | if in == nil { 153 | return nil 154 | } 155 | out := new(ZaProxy) 156 | in.DeepCopyInto(out) 157 | return out 158 | } 159 | -------------------------------------------------------------------------------- /charts/dast-operator/.gitignore: -------------------------------------------------------------------------------- 1 | /charts 2 | -------------------------------------------------------------------------------- /charts/dast-operator/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/dast-operator/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: dast-operator 3 | description: A Helm chart for dast-operator 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.4.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | appVersion: 0.4.3 24 | 25 | home: https://github.com/banzaicloud/dast-operator 26 | maintainers: 27 | - name: BanzaiCloud 28 | email: info@banzaicloud.com 29 | -------------------------------------------------------------------------------- /charts/dast-operator/ci/test-values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for dast-operator. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: banzaicloud/dast-operator 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: "latest" 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: true 20 | # Annotations to add to the service account 21 | annotations: {} 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: "" 25 | 26 | podAnnotations: {} 27 | 28 | podSecurityContext: {} 29 | # fsGroup: 2000 30 | 31 | securityContext: {} 32 | # capabilities: 33 | # drop: 34 | # - ALL 35 | # readOnlyRootFilesystem: true 36 | # runAsNonRoot: true 37 | # runAsUser: 1000 38 | 39 | service: 40 | type: ClusterIP 41 | port: 443 42 | tlsSecretName: "" 43 | 44 | resources: {} 45 | # We usually recommend not to specify default resources and to leave this as a conscious 46 | # choice for the user. This also increases chances charts run on environments with little 47 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 48 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 49 | # limits: 50 | # cpu: 100m 51 | # memory: 128Mi 52 | # requests: 53 | # cpu: 100m 54 | # memory: 128Mi 55 | 56 | nodeSelector: {} 57 | 58 | tolerations: [] 59 | 60 | affinity: {} 61 | -------------------------------------------------------------------------------- /charts/dast-operator/crds/security.banzaicloud.io_dasts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: dasts.security.banzaicloud.io 5 | spec: 6 | group: security.banzaicloud.io 7 | names: 8 | kind: Dast 9 | listKind: DastList 10 | plural: dasts 11 | singular: dast 12 | scope: Namespaced 13 | versions: 14 | - name: v1alpha1 15 | schema: 16 | openAPIV3Schema: 17 | description: Dast is the Schema for the dasts API 18 | properties: 19 | apiVersion: 20 | description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" 21 | type: string 22 | kind: 23 | description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" 24 | type: string 25 | metadata: 26 | type: object 27 | spec: 28 | description: DastSpec defines the desired state of Dast 29 | properties: 30 | analyzer: 31 | properties: 32 | image: 33 | type: string 34 | name: 35 | type: string 36 | service: 37 | description: Service is a named abstraction of software service (for example, mysql) consisting of local port (for example 3306) that the proxy listens on, and the selector that determines which pods will answer requests sent through the proxy. 38 | properties: 39 | apiVersion: 40 | description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" 41 | type: string 42 | kind: 43 | description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" 44 | type: string 45 | metadata: 46 | description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata" 47 | type: object 48 | spec: 49 | description: Spec defines the behavior of a service. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status 50 | properties: 51 | clusterIP: 52 | description: 'clusterIP is the IP address of the service and is usually assigned randomly by the master. If an address is specified manually and is not in use by others, it will be allocated to the service; otherwise, creation of the service will fail. This field can not be changed through updates. Valid values are "None", empty string (""), or a valid IP address. "None" can be specified for headless services when proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' 53 | type: string 54 | externalIPs: 55 | description: externalIPs is a list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP. A common example is external load-balancers that are not part of the Kubernetes system. 56 | items: 57 | type: string 58 | type: array 59 | externalName: 60 | description: externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) and requires Type to be ExternalName. 61 | type: string 62 | externalTrafficPolicy: 63 | description: externalTrafficPolicy denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. "Local" preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. "Cluster" obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading. 64 | type: string 65 | healthCheckNodePort: 66 | description: healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local. 67 | format: int32 68 | type: integer 69 | ipFamily: 70 | description: ipFamily specifies whether this Service has a preference for a particular IP family (e.g. IPv4 vs. IPv6). If a specific IP family is requested, the clusterIP field will be allocated from that family, if it is available in the cluster. If no IP family is requested, the cluster's primary IP family will be used. Other IP fields (loadBalancerIP, loadBalancerSourceRanges, externalIPs) and controllers which allocate external load-balancers should use the same IP family. Endpoints for this Service will be of this family. This field is immutable after creation. Assigning a ServiceIPFamily not available in the cluster (e.g. IPv6 in IPv4 only cluster) is an error condition and will fail during clusterIP assignment. 71 | type: string 72 | loadBalancerIP: 73 | description: "Only applies to Service Type: LoadBalancer LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying the loadBalancerIP when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature." 74 | type: string 75 | loadBalancerSourceRanges: 76 | description: 'If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature." More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' 77 | items: 78 | type: string 79 | type: array 80 | ports: 81 | description: "The list of ports that are exposed by this service. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies" 82 | items: 83 | description: ServicePort contains information on service's port. 84 | properties: 85 | appProtocol: 86 | description: The application protocol for this port. This field follows standard Kubernetes label syntax. Un-prefixed names are reserved for IANA standard service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). Non-standard protocols should use prefixed names such as mycompany.com/my-custom-protocol. Field can be enabled with ServiceAppProtocol feature gate. 87 | type: string 88 | name: 89 | description: The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. When considering the endpoints for a Service, this must match the 'name' field in the EndpointPort. Optional if only one ServicePort is defined on this service. 90 | type: string 91 | nodePort: 92 | description: "The port on each node on which this service is exposed when type=NodePort or LoadBalancer. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the ServiceType of this Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport" 93 | format: int32 94 | type: integer 95 | port: 96 | description: The port that will be exposed by this service. 97 | format: int32 98 | type: integer 99 | protocol: 100 | description: The IP protocol for this port. Supports "TCP", "UDP", and "SCTP". Default is TCP. 101 | type: string 102 | targetPort: 103 | anyOf: 104 | - type: integer 105 | - type: string 106 | description: "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service" 107 | x-kubernetes-int-or-string: true 108 | required: 109 | - port 110 | - protocol 111 | type: object 112 | type: array 113 | x-kubernetes-list-map-keys: 114 | - port 115 | - protocol 116 | x-kubernetes-list-type: map 117 | publishNotReadyAddresses: 118 | description: publishNotReadyAddresses, when set to true, indicates that DNS implementations must publish the notReadyAddresses of subsets for the Endpoints associated with the Service. The default value is false. The primary use case for setting this field is to use a StatefulSet's Headless Service to propagate SRV records for its Pods without respect to their readiness for purpose of peer discovery. 119 | type: boolean 120 | selector: 121 | additionalProperties: 122 | type: string 123 | description: "Route service traffic to pods with label keys and values matching this selector. If empty or not present, the service is assumed to have an external process managing its endpoints, which Kubernetes will not modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/" 124 | type: object 125 | sessionAffinity: 126 | description: 'Supports "ClientIP" and "None". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' 127 | type: string 128 | sessionAffinityConfig: 129 | description: sessionAffinityConfig contains the configurations of session affinity. 130 | properties: 131 | clientIP: 132 | description: clientIP contains the configurations of Client IP based session affinity. 133 | properties: 134 | timeoutSeconds: 135 | description: timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". Default value is 10800(for 3 hours). 136 | format: int32 137 | type: integer 138 | type: object 139 | type: object 140 | topologyKeys: 141 | description: topologyKeys is a preference-order list of topology keys which implementations of services should use to preferentially sort endpoints when accessing this Service, it can not be used at the same time as externalTrafficPolicy=Local. Topology keys must be valid label keys and at most 16 keys may be specified. Endpoints are chosen based on the first topology key with available backends. If this field is specified and all entries have no backends that match the topology of the client, the service has no backends for that client and connections should fail. The special value "*" may be used to mean "any topology". This catch-all value, if used, only makes sense as the last value in the list. If this is not specified or empty, no topology constraints will be applied. 142 | items: 143 | type: string 144 | type: array 145 | type: 146 | description: 'type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. "ExternalName" maps to the specified externalName. "ClusterIP" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object. If clusterIP is "None", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a stable IP. "NodePort" builds on ClusterIP and allocates a port on every node which routes to the clusterIP. "LoadBalancer" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types' 147 | type: string 148 | type: object 149 | status: 150 | description: "Most recently observed status of the service. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" 151 | properties: 152 | loadBalancer: 153 | description: LoadBalancer contains the current status of the load-balancer, if one is present. 154 | properties: 155 | ingress: 156 | description: Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points. 157 | items: 158 | description: "LoadBalancerIngress represents the status of a load-balancer ingress point: traffic intended for the service should be sent to an ingress point." 159 | properties: 160 | hostname: 161 | description: Hostname is set for load-balancer ingress points that are DNS based (typically AWS load-balancers) 162 | type: string 163 | ip: 164 | description: IP is set for load-balancer ingress points that are IP based (typically GCE or OpenStack load-balancers) 165 | type: string 166 | type: object 167 | type: array 168 | type: object 169 | type: object 170 | type: object 171 | target: 172 | type: string 173 | required: 174 | - image 175 | - name 176 | type: object 177 | zaproxy: 178 | description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important: Run "make" to regenerate code after modifying this file' 179 | properties: 180 | apikey: 181 | type: string 182 | config: 183 | items: 184 | type: string 185 | type: array 186 | image: 187 | type: string 188 | name: 189 | type: string 190 | namespace: 191 | type: string 192 | required: 193 | - name 194 | type: object 195 | required: 196 | - zaproxy 197 | type: object 198 | status: 199 | description: DastStatus defines the observed state of Dast 200 | type: object 201 | required: 202 | - spec 203 | type: object 204 | served: true 205 | storage: true 206 | -------------------------------------------------------------------------------- /charts/dast-operator/templates/NOTES.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banzaicloud/dast-operator/9ab1067ce3eb02c667e60e0c62811db99821edbd/charts/dast-operator/templates/NOTES.txt -------------------------------------------------------------------------------- /charts/dast-operator/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "dast-operator.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "dast-operator.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "dast-operator.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "dast-operator.labels" -}} 37 | helm.sh/chart: {{ include "dast-operator.chart" . }} 38 | {{ include "dast-operator.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "dast-operator.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "dast-operator.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "dast-operator.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "dast-operator.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | 64 | {{/* 65 | Create the name of the tls secret to use 66 | */}} 67 | {{- define "dast-operator.tlsSecretName" -}} 68 | {{- if .Values.service.tlsSecretName }} 69 | {{- .Values.secrvice.tlsSecretName }} 70 | {{- else }} 71 | {{- printf "%s-webhook-server-cert" (include "dast-operator.fullname" .) }} 72 | {{- end }} 73 | {{- end }} 74 | -------------------------------------------------------------------------------- /charts/dast-operator/templates/certificate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Issuer 3 | metadata: 4 | name: {{ include "dast-operator.fullname" . }}-selfsigned-issuer 5 | spec: 6 | selfSigned: {} 7 | --- 8 | apiVersion: cert-manager.io/v1 9 | kind: Certificate 10 | metadata: 11 | name: {{ include "dast-operator.fullname" . }}-certificate 12 | spec: 13 | dnsNames: 14 | - "{{ include "dast-operator.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc" 15 | - "{{ include "dast-operator.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc.cluster.local" 16 | issuerRef: 17 | kind: Issuer 18 | name: {{ include "dast-operator.fullname" . }}-selfsigned-issuer 19 | secretName: {{ include "dast-operator.tlsSecretName" . }} 20 | -------------------------------------------------------------------------------- /charts/dast-operator/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "dast-operator.fullname" . }} 5 | labels: 6 | {{- include "dast-operator.labels" . | nindent 4 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | {{- include "dast-operator.selectorLabels" . | nindent 6 }} 12 | template: 13 | metadata: 14 | {{- with .Values.podAnnotations }} 15 | annotations: 16 | {{- toYaml . | nindent 8 }} 17 | {{- end }} 18 | labels: 19 | {{- include "dast-operator.selectorLabels" . | nindent 8 }} 20 | spec: 21 | {{- with .Values.imagePullSecrets }} 22 | imagePullSecrets: 23 | {{- toYaml . | nindent 8 }} 24 | {{- end }} 25 | serviceAccountName: {{ include "dast-operator.serviceAccountName" . }} 26 | securityContext: 27 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 28 | volumes: 29 | - name: cert 30 | secret: 31 | defaultMode: 420 32 | secretName: {{ include "dast-operator.tlsSecretName" . }} 33 | containers: 34 | - name: {{ .Chart.Name }} 35 | securityContext: 36 | {{- toYaml .Values.securityContext | nindent 12 }} 37 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 38 | imagePullPolicy: {{ .Values.image.pullPolicy }} 39 | volumeMounts: 40 | - mountPath: /tmp/k8s-webhook-server/serving-certs 41 | name: cert 42 | readOnly: true 43 | ports: 44 | - name: http 45 | containerPort: 9443 46 | protocol: TCP 47 | livenessProbe: 48 | httpGet: 49 | httpHeaders: 50 | - name: Content-Type 51 | value: application/json 52 | path: /ingress 53 | port: http 54 | scheme: HTTPS 55 | readinessProbe: 56 | httpGet: 57 | httpHeaders: 58 | - name: Content-Type 59 | value: application/json 60 | path: /ingress 61 | port: http 62 | scheme: HTTPS 63 | resources: 64 | {{- toYaml .Values.resources | nindent 12 }} 65 | {{- with .Values.nodeSelector }} 66 | nodeSelector: 67 | {{- toYaml . | nindent 8 }} 68 | {{- end }} 69 | {{- with .Values.affinity }} 70 | affinity: 71 | {{- toYaml . | nindent 8 }} 72 | {{- end }} 73 | {{- with .Values.tolerations }} 74 | tolerations: 75 | {{- toYaml . | nindent 8 }} 76 | {{- end }} 77 | -------------------------------------------------------------------------------- /charts/dast-operator/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ include "dast-operator.fullname" . }}-manager-role 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - secrets 10 | verbs: 11 | - create 12 | - get 13 | - list 14 | - patch 15 | - update 16 | - watch 17 | - apiGroups: 18 | - "" 19 | resources: 20 | - services 21 | verbs: 22 | - create 23 | - get 24 | - list 25 | - patch 26 | - update 27 | - watch 28 | - apiGroups: 29 | - apps 30 | resources: 31 | - deployments 32 | verbs: 33 | - create 34 | - get 35 | - list 36 | - patch 37 | - update 38 | - watch 39 | - apiGroups: 40 | - batch 41 | resources: 42 | - jobs 43 | verbs: 44 | - create 45 | - get 46 | - list 47 | - patch 48 | - update 49 | - watch 50 | - apiGroups: 51 | - security.banzaicloud.io 52 | resources: 53 | - dasts 54 | verbs: 55 | - create 56 | - delete 57 | - get 58 | - list 59 | - patch 60 | - update 61 | - watch 62 | - apiGroups: 63 | - security.banzaicloud.io 64 | resources: 65 | - dasts/status 66 | verbs: 67 | - get 68 | - patch 69 | - update 70 | - watch 71 | --- 72 | apiVersion: rbac.authorization.k8s.io/v1 73 | kind: ClusterRoleBinding 74 | metadata: 75 | name: {{ include "dast-operator.fullname" . }}-manager-rolebinding 76 | roleRef: 77 | apiGroup: rbac.authorization.k8s.io 78 | kind: ClusterRole 79 | name: {{ include "dast-operator.fullname" . }}-manager-role 80 | subjects: 81 | - kind: ServiceAccount 82 | name: {{ include "dast-operator.serviceAccountName" . }} 83 | namespace: {{ .Release.Namespace }} 84 | -------------------------------------------------------------------------------- /charts/dast-operator/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "dast-operator.fullname" . }}-webhook-service 5 | labels: 6 | {{- include "dast-operator.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: 9443 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "dast-operator.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /charts/dast-operator/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "dast-operator.serviceAccountName" . }} 6 | labels: 7 | {{- include "dast-operator.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /charts/dast-operator/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "dast-operator.fullname" . }}-test-connection" 5 | labels: 6 | {{- include "dast-operator.labels" . | nindent 4 }} 7 | annotations: 8 | "helm.sh/hook": test 9 | spec: 10 | containers: 11 | - name: wget 12 | image: busybox 13 | command: ['wget'] 14 | args: ['{{ include "dast-operator.fullname" . }}:{{ .Values.service.port }}'] 15 | restartPolicy: Never 16 | -------------------------------------------------------------------------------- /charts/dast-operator/templates/validating-webhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: ValidatingWebhookConfiguration 3 | metadata: 4 | name: {{ include "dast-operator.fullname" . }}-validating-webhook-configuration 5 | annotations: 6 | cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "dast-operator.fullname" . }}-certificate 7 | webhooks: 8 | - clientConfig: 9 | caBundle: Cg== 10 | service: 11 | name: {{ include "dast-operator.fullname" . }}-webhook-service 12 | namespace: {{.Release.Namespace }} 13 | path: /ingress 14 | failurePolicy: Fail 15 | name: dast.security.banzaicloud.io 16 | rules: 17 | - apiGroups: 18 | - extensions 19 | - networking.k8s.io 20 | apiVersions: 21 | - v1beta1 22 | - v1 23 | operations: 24 | - CREATE 25 | resources: 26 | - ingresses 27 | admissionReviewVersions: 28 | - v1beta1 29 | - v1 30 | sideEffects: None 31 | timeoutSeconds: 5 32 | -------------------------------------------------------------------------------- /charts/dast-operator/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for dast-operator. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 1 6 | 7 | image: 8 | repository: ghcr.io/banzaicloud/dast-operator 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: "" 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: true 20 | # Annotations to add to the service account 21 | annotations: {} 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: "" 25 | 26 | podAnnotations: {} 27 | 28 | podSecurityContext: {} 29 | # fsGroup: 2000 30 | 31 | securityContext: {} 32 | # capabilities: 33 | # drop: 34 | # - ALL 35 | # readOnlyRootFilesystem: true 36 | # runAsNonRoot: true 37 | # runAsUser: 1000 38 | 39 | service: 40 | type: ClusterIP 41 | port: 443 42 | tlsSecretName: "" 43 | 44 | resources: {} 45 | # We usually recommend not to specify default resources and to leave this as a conscious 46 | # choice for the user. This also increases chances charts run on environments with little 47 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 48 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 49 | # limits: 50 | # cpu: 100m 51 | # memory: 128Mi 52 | # requests: 53 | # cpu: 100m 54 | # memory: 128Mi 55 | 56 | nodeSelector: {} 57 | 58 | tolerations: [] 59 | 60 | affinity: {} 61 | -------------------------------------------------------------------------------- /cmd/dynamic-analyzer/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/banzaicloud/dast-operator/cmd/dynamic-analyzer 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/spf13/cobra v1.0.0 7 | github.com/zaproxy/zap-api-go v0.0.0-20200721180916-5fc7048efb18 8 | ) 9 | -------------------------------------------------------------------------------- /cmd/dynamic-analyzer/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 4 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 7 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 8 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 9 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 10 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 11 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 12 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 13 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 14 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 15 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 16 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 19 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 20 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 21 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 22 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 23 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 24 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 25 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 26 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 27 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 28 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 29 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 30 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 31 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 32 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 33 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 34 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 35 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 36 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 37 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 38 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 39 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 40 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 41 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 42 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 43 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 44 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 45 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 46 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 47 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 48 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 49 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 50 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 51 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 52 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 53 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 54 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 55 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 56 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 57 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 58 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 59 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 60 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 61 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 62 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 63 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 64 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 65 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 66 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 67 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 68 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 69 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 70 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 71 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 72 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 73 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 74 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 75 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 76 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 77 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 78 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 79 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 80 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 81 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 82 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 83 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 84 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 85 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 86 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 87 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 88 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 89 | github.com/zaproxy/zap-api-go v0.0.0-20200721180916-5fc7048efb18 h1:BUfxacXzJ2oDk1KgVNslJXIgBYQGkoCRGbQpZW3A9hk= 90 | github.com/zaproxy/zap-api-go v0.0.0-20200721180916-5fc7048efb18/go.mod h1:lBqVZ0hGhjkg0sL/0O7IG1QE+kv3sRQMtpKuyZi5llY= 91 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 92 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 93 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 94 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 95 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 96 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 97 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 98 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 99 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 100 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 101 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 102 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 103 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 104 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 105 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 106 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 107 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 108 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 109 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 110 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 111 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 112 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 113 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 114 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 115 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 116 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 117 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 118 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 119 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 120 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 121 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 122 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 123 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 124 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 125 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 126 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 127 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 128 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 129 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 130 | -------------------------------------------------------------------------------- /cmd/dynamic-analyzer/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | func main() { 20 | execute() 21 | } 22 | -------------------------------------------------------------------------------- /cmd/dynamic-analyzer/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var rootCmd = &cobra.Command{ 27 | Use: "dynamic-analyzer", 28 | Short: "Dynamic analyzer application using Zap", 29 | Long: `This application a DAST solution using Zed Attack Proxy`, 30 | Run: func(cmd *cobra.Command, args []string) { 31 | fmt.Println("rootcmd") 32 | }, 33 | } 34 | 35 | func execute() { 36 | if err := rootCmd.Execute(); err != nil { 37 | fmt.Println(err) 38 | os.Exit(1) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/dynamic-analyzer/scanner.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "log" 23 | "os" 24 | "strconv" 25 | "time" 26 | 27 | "github.com/spf13/cobra" 28 | "github.com/zaproxy/zap-api-go/zap" 29 | ) 30 | 31 | var zapAddr string 32 | var target string 33 | var apiKey string 34 | var serve bool 35 | var openapiURL string 36 | 37 | func NewScannerCmd() *cobra.Command { 38 | cmd := &cobra.Command{ 39 | Use: "scanner", 40 | Short: "Scanner application using Zap", 41 | Run: func(cmd *cobra.Command, args []string) { 42 | scanner() 43 | }, 44 | } 45 | 46 | cmd.Flags().StringVarP(&zapAddr, "zap-proxy", "p", "http://127.0.0.1:8080", "Zap proxy address") 47 | cmd.Flags().StringVarP(&target, "target", "t", "http://127.0.0.1:8090/target", "Target address") 48 | cmd.Flags().StringVarP(&apiKey, "apikey", "a", os.Getenv("ZAPAPIKEY"), "Zap api key") 49 | cmd.Flags().BoolVarP(&serve, "serve", "s", false, "serve results") 50 | 51 | return cmd 52 | } 53 | 54 | func NewApiScannerCmd() *cobra.Command { 55 | cmd := &cobra.Command{ 56 | Use: "apiscan", 57 | Short: "API scanner application using Zap", 58 | Run: func(cmd *cobra.Command, args []string) { 59 | apiScanner() 60 | }, 61 | } 62 | 63 | cmd.Flags().StringVarP(&openapiURL, "openapi", "o", "http://127.0.0.1:8090/swagger.yaml", "Openapi url") 64 | cmd.Flags().StringVarP(&zapAddr, "zap-proxy", "p", "http://127.0.0.1:8080", "Zap proxy address") 65 | cmd.Flags().StringVarP(&target, "target", "t", "http://127.0.0.1:8090/target", "Target address") 66 | cmd.Flags().StringVarP(&apiKey, "apikey", "a", os.Getenv("ZAPAPIKEY"), "Zap api key") 67 | cmd.Flags().BoolVarP(&serve, "serve", "s", false, "serve results") 68 | 69 | return cmd 70 | } 71 | 72 | func init() { 73 | rootCmd.AddCommand(NewScannerCmd()) 74 | rootCmd.AddCommand(NewApiScannerCmd()) 75 | 76 | } 77 | 78 | func scanner() { 79 | cfg := &zap.Config{ 80 | Proxy: zapAddr, 81 | APIKey: apiKey, 82 | } 83 | client, err := zap.NewClient(cfg) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | 88 | // Start spidering the target 89 | fmt.Println("Spider : " + target) 90 | resp, err := client.Spider().Scan(target, "", "", "", "") 91 | if err != nil { 92 | log.Fatal(err) 93 | } 94 | 95 | // The scan now returns a scan id to support concurrent scanning 96 | scanid := resp["scan"].(string) 97 | for { 98 | time.Sleep(1000 * time.Millisecond) 99 | resp, _ = client.Spider().Status(scanid) 100 | progress, _ := strconv.Atoi(resp["status"].(string)) 101 | if progress >= 100 { 102 | break 103 | } 104 | } 105 | fmt.Println("Spider complete") 106 | 107 | // Give the passive scanner a chance to complete 108 | time.Sleep(2000 * time.Millisecond) 109 | 110 | fmt.Println("Active scan : " + target) 111 | resp, err = client.Ascan().Scan(target, "True", "False", "", "", "", "") 112 | if err != nil { 113 | log.Fatal(err) 114 | } 115 | // The scan now returns a scan id to support concurrent scanning 116 | scanid = resp["scan"].(string) 117 | for { 118 | time.Sleep(5000 * time.Millisecond) 119 | resp, _ = client.Ascan().Status(scanid) 120 | progress, _ := strconv.Atoi(resp["status"].(string)) 121 | fmt.Printf("Active Scan progress : %d\n", progress) 122 | if progress >= 100 { 123 | break 124 | } 125 | } 126 | fmt.Println("Active Scan complete") 127 | fmt.Println("Alerts:") 128 | alerts, err := client.Core().Alerts(target, "", "", "") 129 | if err != nil { 130 | log.Fatal(err) 131 | } 132 | summary, err := client.Core().AlertsSummary(target) 133 | if err != nil { 134 | log.Fatal(err) 135 | } 136 | fmt.Printf("alerts: %v", alerts) 137 | fmt.Printf("summary: %v", summary) 138 | jsonString, err := json.Marshal(alerts) 139 | if err != nil { 140 | log.Fatal(err) 141 | } 142 | if serve { 143 | serveResults(jsonString) 144 | } 145 | } 146 | 147 | func apiScanner() { 148 | cfg := &zap.Config{ 149 | Proxy: zapAddr, 150 | APIKey: apiKey, 151 | } 152 | client, err := zap.NewClient(cfg) 153 | if err != nil { 154 | log.Fatal(err) 155 | } 156 | 157 | // Enable scripts 158 | fmt.Println("Loading scripsts...") 159 | client.Script().Load("Alert_on_HTTP_Response_Code_Errors.js", "httpsender", "Oracle Nashorn", "/home/zap/.ZAP_D/scripts/scripts/httpsender/Alert_on_HTTP_Response_Code_Errors.js", "", "") 160 | client.Script().Enable("Alert_on_HTTP_Response_Code_Errors.js") 161 | client.Script().Load("Alert_on_Unexpected_Content_Types.js", "httpsender", "Oracle Nashorn", "/home/zap/.ZAP_D/scripts/scripts/httpsender/Alert_on_Unexpected_Content_Types.js", "", "") 162 | client.Script().Enable("Alert_on_Unexpected_Content_Types.js") 163 | 164 | fmt.Println("Importing openapi URL...") 165 | _, err = client.Openapi().ImportUrl(openapiURL, target) 166 | if err != nil { 167 | log.Fatal(err) 168 | } 169 | urls, err := client.Core().Urls(target) 170 | if err != nil { 171 | log.Fatal(err) 172 | } 173 | 174 | if len(urls) == 0 { 175 | log.Print("Failed to import any URLs") 176 | } 177 | 178 | resp, err := client.Ascan().Scan(target, "True", "False", "", "", "", "") 179 | if err != nil { 180 | log.Fatal(err) 181 | } 182 | // The scan now returns a scan id to support concurrent scanning 183 | scanid := resp["scan"].(string) 184 | for { 185 | time.Sleep(5000 * time.Millisecond) 186 | resp, _ = client.Ascan().Status(scanid) 187 | progress, _ := strconv.Atoi(resp["status"].(string)) 188 | fmt.Printf("Active API Scan progress : %d\n", progress) 189 | if progress >= 100 { 190 | break 191 | } 192 | } 193 | fmt.Println("Active API Scan complete") 194 | fmt.Println("Alerts:") 195 | alerts, err := client.Core().Alerts(target, "", "", "") 196 | if err != nil { 197 | log.Fatal(err) 198 | } 199 | summary, err := client.Core().AlertsSummary(target) 200 | if err != nil { 201 | log.Fatal(err) 202 | } 203 | fmt.Printf("alerts: %v", alerts) 204 | fmt.Printf("summary: %v", summary) 205 | } 206 | -------------------------------------------------------------------------------- /cmd/dynamic-analyzer/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "net/http" 21 | ) 22 | 23 | type scanResult struct { 24 | result []byte 25 | } 26 | 27 | func serveResults(result []byte) { 28 | scanResult := &scanResult{result: result} 29 | http.ListenAndServe(":3000", scanResult) 30 | } 31 | 32 | func (res *scanResult) ServeHTTP(w http.ResponseWriter, r *http.Request) { 33 | w.Write(res.result) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for 4 | # breaking changes 5 | apiVersion: cert-manager.io/v1 6 | kind: Issuer 7 | metadata: 8 | name: selfsigned-issuer 9 | namespace: system 10 | spec: 11 | selfSigned: {} 12 | --- 13 | apiVersion: cert-manager.io/v1 14 | kind: Certificate 15 | metadata: 16 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 17 | namespace: system 18 | spec: 19 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 20 | dnsNames: 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 22 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 23 | issuerRef: 24 | kind: Issuer 25 | name: selfsigned-issuer 26 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 27 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/bases/security.banzaicloud.io_dasts.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.2.5 8 | creationTimestamp: null 9 | name: dasts.security.banzaicloud.io 10 | spec: 11 | group: security.banzaicloud.io 12 | names: 13 | kind: Dast 14 | listKind: DastList 15 | plural: dasts 16 | singular: dast 17 | scope: Namespaced 18 | validation: 19 | openAPIV3Schema: 20 | description: Dast is the Schema for the dasts API 21 | properties: 22 | apiVersion: 23 | description: 'APIVersion defines the versioned schema of this representation 24 | of an object. Servers should convert recognized schemas to the latest 25 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 26 | type: string 27 | kind: 28 | description: 'Kind is a string value representing the REST resource this 29 | object represents. Servers may infer this from the endpoint the client 30 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 31 | type: string 32 | metadata: 33 | type: object 34 | spec: 35 | description: DastSpec defines the desired state of Dast 36 | properties: 37 | analyzer: 38 | properties: 39 | image: 40 | type: string 41 | name: 42 | type: string 43 | service: 44 | description: Service is a named abstraction of software service 45 | (for example, mysql) consisting of local port (for example 3306) 46 | that the proxy listens on, and the selector that determines which 47 | pods will answer requests sent through the proxy. 48 | properties: 49 | apiVersion: 50 | description: 'APIVersion defines the versioned schema of this 51 | representation of an object. Servers should convert recognized 52 | schemas to the latest internal value, and may reject unrecognized 53 | values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 54 | type: string 55 | kind: 56 | description: 'Kind is a string value representing the REST resource 57 | this object represents. Servers may infer this from the endpoint 58 | the client submits requests to. Cannot be updated. In CamelCase. 59 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 60 | type: string 61 | metadata: 62 | description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata' 63 | type: object 64 | spec: 65 | description: Spec defines the behavior of a service. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status 66 | properties: 67 | clusterIP: 68 | description: 'clusterIP is the IP address of the service 69 | and is usually assigned randomly by the master. If an 70 | address is specified manually and is not in use by others, 71 | it will be allocated to the service; otherwise, creation 72 | of the service will fail. This field can not be changed 73 | through updates. Valid values are "None", empty string 74 | (""), or a valid IP address. "None" can be specified for 75 | headless services when proxying is not required. Only 76 | applies to types ClusterIP, NodePort, and LoadBalancer. 77 | Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' 78 | type: string 79 | externalIPs: 80 | description: externalIPs is a list of IP addresses for which 81 | nodes in the cluster will also accept traffic for this 82 | service. These IPs are not managed by Kubernetes. The 83 | user is responsible for ensuring that traffic arrives 84 | at a node with this IP. A common example is external 85 | load-balancers that are not part of the Kubernetes system. 86 | items: 87 | type: string 88 | type: array 89 | externalName: 90 | description: externalName is the external reference that 91 | kubedns or equivalent will return as a CNAME record for 92 | this service. No proxying will be involved. Must be a 93 | valid RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) 94 | and requires Type to be ExternalName. 95 | type: string 96 | externalTrafficPolicy: 97 | description: externalTrafficPolicy denotes if this Service 98 | desires to route external traffic to node-local or cluster-wide 99 | endpoints. "Local" preserves the client source IP and 100 | avoids a second hop for LoadBalancer and Nodeport type 101 | services, but risks potentially imbalanced traffic spreading. 102 | "Cluster" obscures the client source IP and may cause 103 | a second hop to another node, but should have good overall 104 | load-spreading. 105 | type: string 106 | healthCheckNodePort: 107 | description: healthCheckNodePort specifies the healthcheck 108 | nodePort for the service. If not specified, HealthCheckNodePort 109 | is created by the service api backend with the allocated 110 | nodePort. Will use user-specified nodePort value if specified 111 | by the client. Only effects when Type is set to LoadBalancer 112 | and ExternalTrafficPolicy is set to Local. 113 | format: int32 114 | type: integer 115 | ipFamily: 116 | description: ipFamily specifies whether this Service has 117 | a preference for a particular IP family (e.g. IPv4 vs. 118 | IPv6). If a specific IP family is requested, the clusterIP 119 | field will be allocated from that family, if it is available 120 | in the cluster. If no IP family is requested, the cluster's 121 | primary IP family will be used. Other IP fields (loadBalancerIP, 122 | loadBalancerSourceRanges, externalIPs) and controllers 123 | which allocate external load-balancers should use the 124 | same IP family. Endpoints for this Service will be of 125 | this family. This field is immutable after creation. 126 | Assigning a ServiceIPFamily not available in the cluster 127 | (e.g. IPv6 in IPv4 only cluster) is an error condition 128 | and will fail during clusterIP assignment. 129 | type: string 130 | loadBalancerIP: 131 | description: 'Only applies to Service Type: LoadBalancer 132 | LoadBalancer will get created with the IP specified in 133 | this field. This feature depends on whether the underlying 134 | cloud-provider supports specifying the loadBalancerIP 135 | when a load balancer is created. This field will be ignored 136 | if the cloud-provider does not support the feature.' 137 | type: string 138 | loadBalancerSourceRanges: 139 | description: 'If specified and supported by the platform, 140 | this will restrict traffic through the cloud-provider 141 | load-balancer will be restricted to the specified client 142 | IPs. This field will be ignored if the cloud-provider 143 | does not support the feature." More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' 144 | items: 145 | type: string 146 | type: array 147 | ports: 148 | description: 'The list of ports that are exposed by this 149 | service. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' 150 | items: 151 | description: ServicePort contains information on service's 152 | port. 153 | properties: 154 | appProtocol: 155 | description: The application protocol for this port. 156 | This field follows standard Kubernetes label syntax. 157 | Un-prefixed names are reserved for IANA standard 158 | service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). 159 | Non-standard protocols should use prefixed names 160 | such as mycompany.com/my-custom-protocol. Field 161 | can be enabled with ServiceAppProtocol feature gate. 162 | type: string 163 | name: 164 | description: The name of this port within the service. 165 | This must be a DNS_LABEL. All ports within a ServiceSpec 166 | must have unique names. When considering the endpoints 167 | for a Service, this must match the 'name' field 168 | in the EndpointPort. Optional if only one ServicePort 169 | is defined on this service. 170 | type: string 171 | nodePort: 172 | description: 'The port on each node on which this 173 | service is exposed when type=NodePort or LoadBalancer. 174 | Usually assigned by the system. If specified, it 175 | will be allocated to the service if unused or else 176 | creation of the service will fail. Default is to 177 | auto-allocate a port if the ServiceType of this 178 | Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' 179 | format: int32 180 | type: integer 181 | port: 182 | description: The port that will be exposed by this 183 | service. 184 | format: int32 185 | type: integer 186 | protocol: 187 | description: The IP protocol for this port. Supports 188 | "TCP", "UDP", and "SCTP". Default is TCP. 189 | type: string 190 | targetPort: 191 | anyOf: 192 | - type: integer 193 | - type: string 194 | description: 'Number or name of the port to access 195 | on the pods targeted by the service. Number must 196 | be in the range 1 to 65535. Name must be an IANA_SVC_NAME. 197 | If this is a string, it will be looked up as a named 198 | port in the target Pod''s container ports. If this 199 | is not specified, the value of the ''port'' field 200 | is used (an identity map). This field is ignored 201 | for services with clusterIP=None, and should be 202 | omitted or set equal to the ''port'' field. More 203 | info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service' 204 | x-kubernetes-int-or-string: true 205 | required: 206 | - port 207 | - protocol 208 | type: object 209 | type: array 210 | x-kubernetes-list-map-keys: 211 | - port 212 | - protocol 213 | x-kubernetes-list-type: map 214 | publishNotReadyAddresses: 215 | description: publishNotReadyAddresses, when set to true, 216 | indicates that DNS implementations must publish the notReadyAddresses 217 | of subsets for the Endpoints associated with the Service. 218 | The default value is false. The primary use case for setting 219 | this field is to use a StatefulSet's Headless Service 220 | to propagate SRV records for its Pods without respect 221 | to their readiness for purpose of peer discovery. 222 | type: boolean 223 | selector: 224 | additionalProperties: 225 | type: string 226 | description: 'Route service traffic to pods with label keys 227 | and values matching this selector. If empty or not present, 228 | the service is assumed to have an external process managing 229 | its endpoints, which Kubernetes will not modify. Only 230 | applies to types ClusterIP, NodePort, and LoadBalancer. 231 | Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/' 232 | type: object 233 | sessionAffinity: 234 | description: 'Supports "ClientIP" and "None". Used to maintain 235 | session affinity. Enable client IP based session affinity. 236 | Must be ClientIP or None. Defaults to None. More info: 237 | https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' 238 | type: string 239 | sessionAffinityConfig: 240 | description: sessionAffinityConfig contains the configurations 241 | of session affinity. 242 | properties: 243 | clientIP: 244 | description: clientIP contains the configurations of 245 | Client IP based session affinity. 246 | properties: 247 | timeoutSeconds: 248 | description: timeoutSeconds specifies the seconds 249 | of ClientIP type session sticky time. The value 250 | must be >0 && <=86400(for 1 day) if ServiceAffinity 251 | == "ClientIP". Default value is 10800(for 3 hours). 252 | format: int32 253 | type: integer 254 | type: object 255 | type: object 256 | topologyKeys: 257 | description: topologyKeys is a preference-order list of 258 | topology keys which implementations of services should 259 | use to preferentially sort endpoints when accessing this 260 | Service, it can not be used at the same time as externalTrafficPolicy=Local. 261 | Topology keys must be valid label keys and at most 16 262 | keys may be specified. Endpoints are chosen based on the 263 | first topology key with available backends. If this field 264 | is specified and all entries have no backends that match 265 | the topology of the client, the service has no backends 266 | for that client and connections should fail. The special 267 | value "*" may be used to mean "any topology". This catch-all 268 | value, if used, only makes sense as the last value in 269 | the list. If this is not specified or empty, no topology 270 | constraints will be applied. 271 | items: 272 | type: string 273 | type: array 274 | type: 275 | description: 'type determines how the Service is exposed. 276 | Defaults to ClusterIP. Valid options are ExternalName, 277 | ClusterIP, NodePort, and LoadBalancer. "ExternalName" 278 | maps to the specified externalName. "ClusterIP" allocates 279 | a cluster-internal IP address for load-balancing to endpoints. 280 | Endpoints are determined by the selector or if that is 281 | not specified, by manual construction of an Endpoints 282 | object. If clusterIP is "None", no virtual IP is allocated 283 | and the endpoints are published as a set of endpoints 284 | rather than a stable IP. "NodePort" builds on ClusterIP 285 | and allocates a port on every node which routes to the 286 | clusterIP. "LoadBalancer" builds on NodePort and creates 287 | an external load-balancer (if supported in the current 288 | cloud) which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types' 289 | type: string 290 | type: object 291 | status: 292 | description: 'Most recently observed status of the service. 293 | Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status' 294 | properties: 295 | loadBalancer: 296 | description: LoadBalancer contains the current status of 297 | the load-balancer, if one is present. 298 | properties: 299 | ingress: 300 | description: Ingress is a list containing ingress points 301 | for the load-balancer. Traffic intended for the service 302 | should be sent to these ingress points. 303 | items: 304 | description: 'LoadBalancerIngress represents the status 305 | of a load-balancer ingress point: traffic intended 306 | for the service should be sent to an ingress point.' 307 | properties: 308 | hostname: 309 | description: Hostname is set for load-balancer 310 | ingress points that are DNS based (typically 311 | AWS load-balancers) 312 | type: string 313 | ip: 314 | description: IP is set for load-balancer ingress 315 | points that are IP based (typically GCE or OpenStack 316 | load-balancers) 317 | type: string 318 | type: object 319 | type: array 320 | type: object 321 | type: object 322 | type: object 323 | target: 324 | type: string 325 | required: 326 | - image 327 | - name 328 | type: object 329 | zaproxy: 330 | description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 331 | Important: Run "make" to regenerate code after modifying this file' 332 | properties: 333 | apikey: 334 | type: string 335 | config: 336 | items: 337 | type: string 338 | type: array 339 | image: 340 | type: string 341 | name: 342 | type: string 343 | namespace: 344 | type: string 345 | required: 346 | - name 347 | type: object 348 | required: 349 | - zaproxy 350 | type: object 351 | status: 352 | description: DastStatus defines the observed state of Dast 353 | type: object 354 | required: 355 | - spec 356 | type: object 357 | version: v1alpha1 358 | versions: 359 | - name: v1alpha1 360 | served: true 361 | storage: true 362 | status: 363 | acceptedNames: 364 | kind: "" 365 | plural: "" 366 | conditions: [] 367 | storedVersions: [] 368 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/security.banzaicloud.io_dasts.yaml 6 | # +kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_dasts.yaml 12 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- patches/cainjection_in_dasts.yaml 17 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_dasts.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: dasts.security.banzaicloud.io 9 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_dasts.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: dasts.security.banzaicloud.io 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: dast-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: dast-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | - ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | - ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | # - ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | # - manager_auth_proxy_patch.yaml 32 | 33 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 34 | # crd/kustomization.yaml 35 | - manager_webhook_patch.yaml 36 | 37 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 38 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 39 | # 'CERTMANAGER' needs to be enabled to use ca injection 40 | - webhookcainjection_patch.yaml 41 | 42 | # the following config is for teaching kustomize how to do var substitution 43 | vars: 44 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 45 | - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 46 | objref: 47 | kind: Certificate 48 | group: cert-manager.io 49 | version: v1 50 | name: serving-cert # this name should match the one in certificate.yaml 51 | fieldref: 52 | fieldpath: metadata.namespace 53 | - name: CERTIFICATE_NAME 54 | objref: 55 | kind: Certificate 56 | group: cert-manager.io 57 | version: v1 58 | name: serving-cert # this name should match the one in certificate.yaml 59 | - name: SERVICE_NAMESPACE # namespace of the service 60 | objref: 61 | kind: Service 62 | version: v1 63 | name: webhook-service 64 | fieldref: 65 | fieldpath: metadata.namespace 66 | - name: SERVICE_NAME 67 | objref: 68 | kind: Service 69 | version: v1 70 | name: webhook-service 71 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--metrics-addr=127.0.0.1:8080" 25 | - "--enable-leader-election" 26 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | # apiVersion: admissionregistration.k8s.io/v1beta1 4 | # kind: MutatingWebhookConfiguration 5 | # metadata: 6 | # name: mutating-webhook-configuration 7 | # annotations: 8 | # cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | # --- 10 | apiVersion: admissionregistration.k8s.io/v1beta1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: banzaicloud/dast-operator 8 | newTag: latest 9 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | containers: 26 | - command: 27 | - /manager 28 | args: 29 | - --enable-leader-election 30 | image: controller:latest 31 | imagePullPolicy: IfNotPresent 32 | name: manager 33 | resources: 34 | limits: 35 | cpu: 100m 36 | memory: 30Mi 37 | requests: 38 | cpu: 100m 39 | memory: 20Mi 40 | terminationGracePeriodSeconds: 10 41 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | selector: 15 | matchLabels: 16 | control-plane: controller-manager 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/dast_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit dasts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dast-editor-role 6 | rules: 7 | - apiGroups: 8 | - security.banzaicloud.io 9 | resources: 10 | - dasts 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - security.banzaicloud.io 21 | resources: 22 | - dasts/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/dast_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view dasts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dast-viewer-role 6 | rules: 7 | - apiGroups: 8 | - security.banzaicloud.io 9 | resources: 10 | - dasts 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - security.banzaicloud.io 17 | resources: 18 | - dasts/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 4 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | - auth_proxy_client_clusterrole.yaml 13 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - "" 11 | resources: 12 | - secrets 13 | verbs: 14 | - create 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - services 24 | verbs: 25 | - create 26 | - get 27 | - list 28 | - patch 29 | - update 30 | - watch 31 | - apiGroups: 32 | - apps 33 | resources: 34 | - deployments 35 | verbs: 36 | - create 37 | - get 38 | - list 39 | - patch 40 | - update 41 | - watch 42 | - apiGroups: 43 | - batch 44 | resources: 45 | - jobs 46 | verbs: 47 | - create 48 | - get 49 | - list 50 | - patch 51 | - update 52 | - watch 53 | - apiGroups: 54 | - security.banzaicloud.io 55 | resources: 56 | - dasts 57 | verbs: 58 | - create 59 | - delete 60 | - get 61 | - list 62 | - patch 63 | - update 64 | - watch 65 | - apiGroups: 66 | - security.banzaicloud.io 67 | resources: 68 | - dasts/status 69 | verbs: 70 | - get 71 | - patch 72 | - update 73 | - watch 74 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/samples/security_v1alpha1_dast.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: security.banzaicloud.io/v1alpha1 2 | kind: Dast 3 | metadata: 4 | name: dast-sample 5 | spec: 6 | zaproxy: 7 | name: dast-test 8 | apikey: abcd1234 9 | -------------------------------------------------------------------------------- /config/samples/security_v1alpha1_dast_external.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: security.banzaicloud.io/v1alpha1 2 | kind: Dast 3 | metadata: 4 | name: dast-sample-external 5 | spec: 6 | zaproxy: 7 | name: dast-test-external 8 | apikey: abcd1234 9 | analyzer: 10 | image: banzaicloud/dast-analyzer:latest 11 | name: external-test 12 | target: https://example.com 13 | -------------------------------------------------------------------------------- /config/samples/security_v1alpha1_dast_extraconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: security.banzaicloud.io/v1alpha1 2 | kind: Dast 3 | metadata: 4 | name: dast-sample 5 | spec: 6 | zaproxy: 7 | name: dast-test 8 | apikey: abcd1234 9 | config: 10 | - "replacer.full_list(0).description=auth" 11 | - "replacer.full_list(0).enabled=true" 12 | - "replacer.full_list(0).matchtype=REQ_HEADER" 13 | - "replacer.full_list(0).matchstr=Authorization" 14 | - "replacer.full_list(0).regex=false" 15 | - "replacer.full_list(0).replacement=Bearer AbCdEf123456" 16 | - "formhandler.fields.field(0).fieldId=id" 17 | - "formhandler.fields.field(0).value=example-todo-id" 18 | - "formhandler.fields.field(0).enabled=true" 19 | -------------------------------------------------------------------------------- /config/samples/test-api.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test-api 5 | labels: 6 | app: mga 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: mga 12 | secscan: dast 13 | template: 14 | metadata: 15 | labels: 16 | app: mga 17 | secscan: dast 18 | spec: 19 | containers: 20 | - name: modern-go-application 21 | image: sagikazarmark/modern-go-application:latest 22 | command: ["modern-go-application"] 23 | args: ["--telemetry-addr", ":10000", "--http-addr", ":8000", "--grpc-addr", ":8001", "--config", "/config/config.toml"] 24 | ports: 25 | - name: app 26 | containerPort: 8000 27 | - name: telemetry 28 | containerPort: 10000 29 | - name: grpc 30 | containerPort: 8001 31 | volumeMounts: 32 | - name: config 33 | mountPath: /config 34 | volumes: 35 | - name: config 36 | configMap: 37 | name: mga-config 38 | items: 39 | - key: config.toml 40 | path: config.toml 41 | --- 42 | apiVersion: v1 43 | kind: Service 44 | metadata: 45 | name: test-api-service 46 | annotations: 47 | dast.security.banzaicloud.io/zaproxy: "dast-test" 48 | dast.security.banzaicloud.io/zaproxy-namespace: "zaproxy" 49 | dast.security.banzaicloud.io/apiscan: "true" 50 | dast.security.banzaicloud.io/openapi-url: "https://raw.githubusercontent.com/sagikazarmark/modern-go-application/master/api/openapi/todo/openapi.yaml" 51 | spec: 52 | selector: 53 | app: mga 54 | secscan: dast 55 | ports: 56 | - port: 8000 57 | targetPort: 8000 58 | --- 59 | apiVersion: v1 60 | kind: ConfigMap 61 | metadata: 62 | name: mga-config 63 | data: 64 | config.toml: | 65 | [log] 66 | format = "json" 67 | level = "info" 68 | 69 | [telemetry] 70 | addr = ":10000" 71 | 72 | [opencensus.exporter] 73 | enabled = false 74 | address = "127.0.0.1:55678" 75 | insecure = false 76 | reconnectPeriod = "5s" 77 | 78 | [opencensus.trace] 79 | sampling = { sampler = "always" } 80 | # sampling = { sampler = "probability", fraction = 0.5 } 81 | 82 | [opencensus.prometheus] 83 | enabled = false 84 | 85 | [app] 86 | httpAddr = ":8000" 87 | grpcAddr = ":8001" 88 | 89 | storage = "inmemory" 90 | 91 | [database] 92 | host = "localhost" 93 | port = 3306 94 | user = "root" 95 | pass = "" 96 | name = "app" 97 | params = { collation = "utf8mb4_general_ci" } 98 | -------------------------------------------------------------------------------- /config/samples/test_ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: test-ingress 5 | annotations: 6 | dast.security.banzaicloud.io/medium: "2" 7 | dast.security.banzaicloud.io/low: "5" 8 | dast.security.banzaicloud.io/informational: "10" 9 | nginx.ingress.kubernetes.io/rewrite-target: / 10 | spec: 11 | rules: 12 | - http: 13 | paths: 14 | - path: / 15 | backend: 16 | serviceName: test-service 17 | servicePort: 80 18 | -------------------------------------------------------------------------------- /config/samples/test_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: test-deployment 5 | namespace: test 6 | labels: 7 | app: nginx 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: nginx 13 | secscan: dast 14 | template: 15 | metadata: 16 | labels: 17 | app: nginx 18 | secscan: dast 19 | spec: 20 | containers: 21 | - name: nginx 22 | image: nginx:1.16.0-alpine 23 | ports: 24 | - containerPort: 80 25 | --- 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | name: test-service 30 | namespace: test 31 | annotations: 32 | dast.security.banzaicloud.io/zaproxy: "dast-test" 33 | dast.security.banzaicloud.io/zaproxy-namespace: "zaproxy" 34 | spec: 35 | selector: 36 | app: nginx 37 | secscan: dast 38 | ports: 39 | - port: 80 40 | targetPort: 80 41 | -------------------------------------------------------------------------------- /config/samples/test_v1_ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: test-ingress 5 | annotations: 6 | dast.security.banzaicloud.io/medium: "2" 7 | dast.security.banzaicloud.io/low: "5" 8 | dast.security.banzaicloud.io/informational: "10" 9 | nginx.ingress.kubernetes.io/rewrite-target: / 10 | spec: 11 | rules: 12 | - http: 13 | paths: 14 | - path: / 15 | pathType: Prefix 16 | backend: 17 | service: 18 | name: test-service 19 | port: 20 | number: 80 21 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: ValidatingWebhookConfiguration 5 | metadata: 6 | creationTimestamp: null 7 | name: validating-webhook-configuration 8 | webhooks: 9 | - clientConfig: 10 | caBundle: Cg== 11 | service: 12 | name: webhook-service 13 | namespace: system 14 | path: /ingress 15 | failurePolicy: Fail 16 | name: dast.security.banzaicloud.io 17 | rules: 18 | - apiGroups: 19 | - extensions 20 | - networking.k8s.io 21 | apiVersions: 22 | - v1beta1 23 | - v1 24 | operations: 25 | - CREATE 26 | resources: 27 | - ingresses 28 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 9443 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /controllers/dast_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/go-logr/logr" 23 | apierrors "k8s.io/apimachinery/pkg/api/errors" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | ctrl "sigs.k8s.io/controller-runtime" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | 28 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 29 | "github.com/banzaicloud/dast-operator/pkg/resources" 30 | "github.com/banzaicloud/dast-operator/pkg/resources/analyzer" 31 | "github.com/banzaicloud/dast-operator/pkg/resources/zaproxy" 32 | ) 33 | 34 | // DastReconciler reconciles a Dast object 35 | type DastReconciler struct { 36 | client.Client 37 | Log logr.Logger 38 | Scheme *runtime.Scheme 39 | } 40 | 41 | // +kubebuilder:rbac:groups=security.banzaicloud.io,resources=dasts,verbs=get;list;watch;create;update;patch;delete 42 | // +kubebuilder:rbac:groups=security.banzaicloud.io,resources=dasts/status,verbs=get;update;patch;watch 43 | // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;create;list;update;patch;watch 44 | // +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;create;list;update;patch;watch 45 | // +kubebuilder:rbac:groups="",resources=services,verbs=get;create;list;update;patch;watch 46 | // +kubebuilder:rbac:groups="",resources=secrets,verbs=get;create;list;update;patch;watch 47 | 48 | func (r *DastReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 49 | ctx := context.Background() 50 | log := r.Log.WithValues("dast", req.NamespacedName) 51 | 52 | var dast securityv1alpha1.Dast 53 | if err := r.Get(ctx, req.NamespacedName, &dast); err != nil { 54 | if apierrors.IsNotFound(err) { 55 | // we'll ignore not-found errors, since they can't be fixed by an immediate 56 | // requeue (we'll need to wait for a new notification), and we can get them 57 | // on deleted requests. 58 | return ctrl.Result{}, nil 59 | } 60 | return ctrl.Result{}, err 61 | } 62 | 63 | reconcilers := []resources.ComponentReconciler{ 64 | zaproxy.New(r.Client, &dast), 65 | } 66 | if dast.Spec.Analyzer.Name != "" { 67 | reconcilers = append(reconcilers, analyzer.New(r.Client, &dast)) 68 | } 69 | 70 | for _, rec := range reconcilers { 71 | err := rec.Reconcile(log) 72 | if err != nil { 73 | return ctrl.Result{}, err 74 | } 75 | } 76 | return ctrl.Result{}, nil 77 | } 78 | 79 | func (r *DastReconciler) SetupWithManager(mgr ctrl.Manager) error { 80 | return ctrl.NewControllerManagedBy(mgr). 81 | For(&securityv1alpha1.Dast{}). 82 | Complete(r) 83 | } 84 | -------------------------------------------------------------------------------- /controllers/service_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/go-logr/logr" 23 | corev1 "k8s.io/api/core/v1" 24 | apierrors "k8s.io/apimachinery/pkg/api/errors" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | 30 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 31 | "github.com/banzaicloud/dast-operator/pkg/k8sutil" 32 | "github.com/banzaicloud/dast-operator/pkg/resources" 33 | "github.com/banzaicloud/dast-operator/pkg/resources/analyzer" 34 | ) 35 | 36 | // ServiceReconciler reconciles a Service object 37 | type ServiceReconciler struct { 38 | client.Client 39 | Log logr.Logger 40 | Scheme *runtime.Scheme 41 | } 42 | 43 | // +kubebuilder:rbac:groups="",resources=services,verbs=get;create;list;update;patch;watch 44 | 45 | func (r *ServiceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 46 | ctx := context.Background() 47 | log := r.Log.WithValues("service", req.NamespacedName) 48 | 49 | var service corev1.Service 50 | if err := r.Get(ctx, req.NamespacedName, &service); err != nil { 51 | if apierrors.IsNotFound(err) { 52 | // we'll ignore not-found errors, since they can't be fixed by an immediate 53 | // requeue (we'll need to wait for a new notification), and we can get them 54 | // on deleted requests. 55 | return ctrl.Result{}, nil 56 | } 57 | return ctrl.Result{}, err 58 | } 59 | 60 | zaProxyCfg, err := k8sutil.GetServiceAnotations(&service, log) 61 | if err != nil { 62 | return ctrl.Result{}, nil 63 | } 64 | 65 | log.Info("service reconciler", "serrvice", service.Spec) 66 | 67 | ann := securityv1alpha1.Dast{ 68 | ObjectMeta: metav1.ObjectMeta{ 69 | Name: service.GetName(), 70 | Namespace: zaProxyCfg["namespace"], 71 | }, 72 | Spec: securityv1alpha1.DastSpec{ 73 | ZaProxy: securityv1alpha1.ZaProxy{ 74 | Name: zaProxyCfg["name"], 75 | }, 76 | Analyzer: securityv1alpha1.Analyzer{ 77 | Image: zaProxyCfg["analyzer_image"], 78 | Name: service.GetName(), 79 | Target: k8sutil.GetTargetService(&service), 80 | Service: &service, 81 | }, 82 | }, 83 | } 84 | 85 | reconcilers := []resources.ComponentReconciler{ 86 | analyzer.New(r.Client, &ann), 87 | } 88 | 89 | for _, rec := range reconcilers { 90 | err := rec.Reconcile(log) 91 | if err != nil { 92 | return ctrl.Result{}, err 93 | } 94 | } 95 | 96 | return ctrl.Result{}, nil 97 | } 98 | 99 | func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { 100 | return ctrl.NewControllerManagedBy(mgr). 101 | For(&corev1.Service{}). 102 | Complete(r) 103 | } 104 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | "k8s.io/client-go/rest" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest" 29 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 30 | logf "sigs.k8s.io/controller-runtime/pkg/log" 31 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 32 | 33 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 34 | // +kubebuilder:scaffold:imports 35 | ) 36 | 37 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 38 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 39 | 40 | var cfg *rest.Config 41 | var k8sClient client.Client 42 | var testEnv *envtest.Environment 43 | 44 | func TestAPIs(t *testing.T) { 45 | RegisterFailHandler(Fail) 46 | 47 | RunSpecsWithDefaultAndCustomReporters(t, 48 | "Controller Suite", 49 | []Reporter{printer.NewlineReporter{}}) 50 | } 51 | 52 | var _ = BeforeSuite(func(done Done) { 53 | logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) 54 | 55 | By("bootstrapping test environment") 56 | testEnv = &envtest.Environment{ 57 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 58 | } 59 | 60 | var err error 61 | cfg, err = testEnv.Start() 62 | Expect(err).ToNot(HaveOccurred()) 63 | Expect(cfg).ToNot(BeNil()) 64 | 65 | err = securityv1alpha1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | // +kubebuilder:scaffold:scheme 69 | 70 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 71 | Expect(err).ToNot(HaveOccurred()) 72 | Expect(k8sClient).ToNot(BeNil()) 73 | 74 | close(done) 75 | }, 60) 76 | 77 | var _ = AfterSuite(func() { 78 | By("tearing down the test environment") 79 | err := testEnv.Stop() 80 | Expect(err).ToNot(HaveOccurred()) 81 | }) 82 | -------------------------------------------------------------------------------- /docs/images/dast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banzaicloud/dast-operator/9ab1067ce3eb02c667e60e0c62811db99821edbd/docs/images/dast.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/banzaicloud/dast-operator 2 | 3 | go 1.16 4 | 5 | require ( 6 | emperror.dev/emperror v0.33.0 7 | emperror.dev/errors v0.8.0 8 | github.com/go-logr/logr v0.3.0 9 | github.com/go-logr/zapr v0.3.0 // indirect 10 | github.com/gogo/protobuf v1.3.2 // indirect 11 | github.com/onsi/ginkgo v1.12.1 12 | github.com/onsi/gomega v1.10.1 13 | github.com/spf13/cast v1.3.0 14 | github.com/zaproxy/zap-api-go v0.0.0-20200721180916-5fc7048efb18 15 | istio.io/pkg v0.0.0-20200603210349-955e16c6198a 16 | k8s.io/api v0.19.4 17 | k8s.io/apimachinery v0.19.4 18 | k8s.io/client-go v0.19.4 19 | sigs.k8s.io/controller-runtime v0.6.4 20 | ) 21 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /hack/check-state.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sleep 5 4 | 5 | state="start" 6 | while true; do 7 | case "$state" in 8 | "start") 9 | echo "initial state" 10 | state="pending" 11 | ;; 12 | "pending") 13 | echo "pending state" 14 | kubectl get po -l app.kubernetes.io/component=webhook -n cert-manager -o jsonpath='{.items[*].status.conditions[*].status}' | grep -i false 15 | status=$? 16 | if [ $status != 1 ]; then 17 | sleep 5 18 | continue 19 | fi 20 | state="running" 21 | ;; 22 | "running") 23 | echo "webhook is running" 24 | break 25 | ;; 26 | *) 27 | echo "invalid state \"$state\"" 28 | break 29 | ;; 30 | esac 31 | done 32 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | 23 | appsv1 "k8s.io/api/apps/v1" 24 | batchv1 "k8s.io/api/batch/v1" 25 | corev1 "k8s.io/api/core/v1" 26 | extv1beta1 "k8s.io/api/extensions/v1beta1" 27 | "k8s.io/apimachinery/pkg/runtime" 28 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 29 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 30 | ctrl "sigs.k8s.io/controller-runtime" 31 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 32 | "sigs.k8s.io/controller-runtime/pkg/webhook" 33 | 34 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 35 | "github.com/banzaicloud/dast-operator/controllers" 36 | "github.com/banzaicloud/dast-operator/webhooks" 37 | // +kubebuilder:scaffold:imports 38 | ) 39 | 40 | var ( 41 | scheme = runtime.NewScheme() 42 | setupLog = ctrl.Log.WithName("setup") 43 | ) 44 | 45 | func init() { 46 | _ = clientgoscheme.AddToScheme(scheme) 47 | _ = securityv1alpha1.AddToScheme(scheme) 48 | _ = appsv1.AddToScheme(scheme) 49 | _ = batchv1.AddToScheme(scheme) 50 | _ = corev1.AddToScheme(scheme) 51 | _ = securityv1alpha1.AddToScheme(scheme) 52 | _ = extv1beta1.AddToScheme(scheme) 53 | // +kubebuilder:scaffold:scheme 54 | } 55 | 56 | func main() { 57 | var metricsAddr string 58 | var enableLeaderElection bool 59 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 60 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 61 | "Enable leader election for controller manager. "+ 62 | "Enabling this will ensure there is only one active controller manager.") 63 | flag.Parse() 64 | 65 | ctrl.SetLogger(zap.New(zap.UseDevMode(true))) 66 | 67 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 68 | Scheme: scheme, 69 | MetricsBindAddress: metricsAddr, 70 | Port: 9443, 71 | LeaderElection: enableLeaderElection, 72 | LeaderElectionID: "a7efd836.banzaicloud.io", 73 | }) 74 | if err != nil { 75 | setupLog.Error(err, "unable to start manager") 76 | os.Exit(1) 77 | } 78 | 79 | if err = (&controllers.DastReconciler{ 80 | Client: mgr.GetClient(), 81 | Log: ctrl.Log.WithName("controllers").WithName("Dast"), 82 | Scheme: mgr.GetScheme(), 83 | }).SetupWithManager(mgr); err != nil { 84 | setupLog.Error(err, "unable to create controller", "controller", "Dast") 85 | os.Exit(1) 86 | } 87 | err = (&controllers.ServiceReconciler{ 88 | Client: mgr.GetClient(), 89 | Log: ctrl.Log.WithName("controllers").WithName("Service"), 90 | }).SetupWithManager(mgr) 91 | if err != nil { 92 | setupLog.Error(err, "unable to create controller", "controller", "Service") 93 | os.Exit(1) 94 | } 95 | 96 | // Setup webhooks 97 | if os.Getenv("ENABLE_WEBHOOKS") != "false" { 98 | setupLog.Info("setting up webhook server") 99 | hookServer := mgr.GetWebhookServer() 100 | 101 | setupLog.Info("registering webhooks to the webhook server") 102 | hookServer.Register("/ingress", &webhook.Admission{Handler: webhooks.NewIngressValidator(mgr.GetClient(), ctrl.Log.WithName("webhooks").WithName("Ingress"))}) 103 | } 104 | 105 | // +kubebuilder:scaffold:builder 106 | 107 | setupLog.Info("starting manager") 108 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 109 | setupLog.Error(err, "problem running manager") 110 | os.Exit(1) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /pkg/k8sutil/deployment.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8sutil 18 | 19 | import ( 20 | "github.com/go-logr/logr" 21 | appsv1 "k8s.io/api/apps/v1" 22 | corev1 "k8s.io/api/core/v1" 23 | ) 24 | 25 | func GetDeploymentStatusAvailable(deployment *appsv1.Deployment, log logr.Logger) bool { 26 | 27 | for _, c := range deployment.Status.Conditions { 28 | if c.Type == appsv1.DeploymentAvailable && c.Status == corev1.ConditionTrue { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | -------------------------------------------------------------------------------- /pkg/k8sutil/resource.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8sutil 18 | 19 | import ( 20 | "context" 21 | "reflect" 22 | 23 | "emperror.dev/emperror" 24 | "github.com/go-logr/logr" 25 | apierrors "k8s.io/apimachinery/pkg/api/errors" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | runtimeClient "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 30 | ) 31 | 32 | // Reconcile reconciles K8S resources 33 | func Reconcile(log logr.Logger, client runtimeClient.Client, desired runtime.Object, cr *securityv1alpha1.Dast) error { 34 | desiredType := reflect.TypeOf(desired) 35 | var current = desired.DeepCopyObject() 36 | var err error 37 | 38 | switch desired.(type) { 39 | default: 40 | var key runtimeClient.ObjectKey 41 | key, err = runtimeClient.ObjectKeyFromObject(current) 42 | if err != nil { 43 | return emperror.With(err, "kind", desiredType) 44 | } 45 | log = log.WithValues("kind", desiredType, "name", key.Name) 46 | 47 | err = client.Get(context.TODO(), key, current) 48 | if err != nil && !apierrors.IsNotFound(err) { 49 | return emperror.WrapWith(err, "getting resource failed", "kind", desiredType, "name", key.Name) 50 | } 51 | if apierrors.IsNotFound(err) { 52 | if err := client.Create(context.TODO(), desired); err != nil { 53 | return emperror.WrapWith(err, "creating resource failed", "kind", desiredType, "name", key.Name) 54 | } 55 | log.Info("resource created") 56 | return nil 57 | } 58 | } 59 | return nil 60 | } 61 | -------------------------------------------------------------------------------- /pkg/k8sutil/secret.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8sutil 18 | 19 | import ( 20 | "context" 21 | 22 | "emperror.dev/emperror" 23 | "github.com/go-logr/logr" 24 | corev1 "k8s.io/api/core/v1" 25 | "k8s.io/apimachinery/pkg/types" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | ) 28 | 29 | func GetSercretByName(name, namespace string, client client.Client, log logr.Logger) (*corev1.Secret, error) { 30 | key := types.NamespacedName{ 31 | Name: name, 32 | Namespace: namespace, 33 | } 34 | log.Info("secret getter", "name", name, "namespace", namespace) 35 | var secret corev1.Secret 36 | if err := client.Get(context.TODO(), key, &secret); err != nil { 37 | return nil, emperror.Wrap(err, "cannot get secret by name") 38 | } 39 | 40 | return &secret, nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/k8sutil/service.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package k8sutil 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "strconv" 23 | 24 | "emperror.dev/emperror" 25 | "emperror.dev/errors" 26 | "github.com/go-logr/logr" 27 | corev1 "k8s.io/api/core/v1" 28 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 | "k8s.io/apimachinery/pkg/types" 30 | "sigs.k8s.io/controller-runtime/pkg/client" 31 | ) 32 | 33 | func GetServiceStatus(service *corev1.Service) bool { 34 | // TODO improve service status check 35 | if service.Spec.ClusterIP != "" { 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | func GetTargetService(service *corev1.Service) string { 42 | var portNR string 43 | // TODO handle multiple port 44 | for _, port := range service.Spec.Ports { 45 | portNR = strconv.Itoa(int(port.Port)) 46 | } 47 | // TODO handle protocol 48 | return "http://" + service.GetName() + "." + service.GetNamespace() + ".svc.cluster.local:" + portNR 49 | } 50 | 51 | func GetIngressBackendServices(ingress *unstructured.Unstructured, log logr.Logger) ([]map[string]string, error) { 52 | log.Info("ingress", "ingress", ingress) 53 | backends := []map[string]string{} 54 | rules, ok, _ := unstructured.NestedSlice(ingress.Object, "spec", "rules") 55 | if !ok { 56 | return backends, errors.New("value not found: rules") 57 | } 58 | for _, rule := range rules { 59 | paths, ok, _ := unstructured.NestedSlice(rule.(map[string]interface{}), "http", "paths") 60 | if !ok { 61 | return backends, errors.New("value not found: paths") 62 | } 63 | for _, path := range paths { 64 | backend := map[string]string{} 65 | backend["name"], ok, _ = unstructured.NestedString(path.(map[string]interface{}), "backend", "serviceName") 66 | if !ok { 67 | backend["name"], ok, _ = unstructured.NestedString(path.(map[string]interface{}), "backend", "service", "name") 68 | if !ok { 69 | return backends, errors.New("value not found: service name") 70 | } 71 | } 72 | portNum, ok, _ := unstructured.NestedFieldCopy(path.(map[string]interface{}), "backend", "servicePort") 73 | if !ok { 74 | portNum, ok, _ = unstructured.NestedFieldCopy(path.(map[string]interface{}), "backend", "service", "port", "number") 75 | if !ok { 76 | return backends, errors.New("value not found: service port") 77 | } 78 | } 79 | 80 | backend["port"] = fmt.Sprintf("%v", portNum) 81 | backends = append(backends, backend) 82 | } 83 | } 84 | 85 | return backends, nil 86 | } 87 | 88 | func GetServiceByName(name, namespace string, client client.Client) (*corev1.Service, error) { 89 | key := types.NamespacedName{ 90 | Name: name, 91 | Namespace: namespace, 92 | } 93 | var service corev1.Service 94 | if err := client.Get(context.TODO(), key, &service); err != nil { 95 | return nil, emperror.Wrap(err, "cannot get service by name") 96 | } 97 | 98 | return &service, nil 99 | } 100 | 101 | func GetServiceAnotations(service *corev1.Service, log logr.Logger) (map[string]string, error) { 102 | annotations := service.GetAnnotations() 103 | zaProxyCfg := map[string]string{} 104 | if zaproxyName, ok := annotations["dast.security.banzaicloud.io/zaproxy"]; ok { 105 | zaProxyCfg["name"] = zaproxyName 106 | zaProxyCfg["namespace"], ok = annotations["dast.security.banzaicloud.io/zaproxy-namespace"] 107 | if !ok { 108 | zaProxyCfg["namespace"] = service.GetNamespace() 109 | log.Info("missing zaproxy namespace annotation, using service namespace", "ns_name", zaProxyCfg["namespace"]) 110 | } 111 | zaProxyCfg["analyzer_image"], ok = annotations["dast.security.banzaicloud.io/analyzer_image"] 112 | if !ok { 113 | zaProxyCfg["analyzer_image"] = "ghcr.io/banzaicloud/dast-analyzer:latest" 114 | log.Info("missing zaproxy analyzer image annotation, using ", "analyzer_image", zaProxyCfg["analyzer_image"]) 115 | } 116 | return zaProxyCfg, nil 117 | } 118 | 119 | return nil, errors.New("service isn't annotated") 120 | } 121 | -------------------------------------------------------------------------------- /pkg/resources/analyzer/analyzer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package analyzer 18 | 19 | import ( 20 | "context" 21 | "time" 22 | 23 | "emperror.dev/emperror" 24 | "github.com/go-logr/logr" 25 | appsv1 "k8s.io/api/apps/v1" 26 | corev1 "k8s.io/api/core/v1" 27 | "k8s.io/apimachinery/pkg/types" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | 30 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 31 | "github.com/banzaicloud/dast-operator/pkg/k8sutil" 32 | "github.com/banzaicloud/dast-operator/pkg/resources" 33 | ) 34 | 35 | const ( 36 | componentName = "analyzer" 37 | ) 38 | 39 | var labelSelector = map[string]string{ 40 | "app": "analyzer", 41 | } 42 | 43 | // Reconciler implements the Component Reconciler 44 | type Reconciler struct { 45 | resources.Reconciler 46 | } 47 | 48 | // New creates a new reconciler for analyzer 49 | func New(client client.Client, dast *securityv1alpha1.Dast) *Reconciler { 50 | return &Reconciler{ 51 | Reconciler: resources.Reconciler{ 52 | Client: client, 53 | Dast: dast, 54 | }, 55 | } 56 | } 57 | 58 | // Reconcile implements the reconcile logic for analyzer 59 | func (r *Reconciler) Reconcile(log logr.Logger) error { 60 | log = log.WithValues("component", componentName) 61 | 62 | log.V(1).Info("Reconciling") 63 | 64 | key := types.NamespacedName{ 65 | Name: r.Dast.Spec.ZaProxy.Name, 66 | Namespace: r.Dast.Namespace, 67 | } 68 | 69 | zapDeployment := appsv1.Deployment{} 70 | if err := r.Get(context.TODO(), key, &zapDeployment); err != nil { 71 | return emperror.Wrap(err, "failed to get zap deployment") 72 | } 73 | 74 | if func(deployment *appsv1.Deployment) bool { 75 | timeout := time.After(1 * time.Minute) 76 | ticker := time.NewTicker(500 * time.Millisecond) 77 | for { 78 | select { 79 | case <-timeout: 80 | return false 81 | case <-ticker.C: 82 | r.Get(context.TODO(), key, deployment) 83 | if k8sutil.GetDeploymentStatusAvailable(deployment, log) { 84 | return true 85 | } 86 | } 87 | } 88 | }(&zapDeployment) { 89 | log.Info("deployment is available") 90 | } 91 | 92 | if r.Dast.Spec.Analyzer.Service != nil { 93 | key := types.NamespacedName{ 94 | Name: r.Dast.Spec.Analyzer.Service.GetName(), 95 | Namespace: r.Dast.Spec.Analyzer.Service.GetNamespace(), 96 | } 97 | 98 | service := corev1.Service{} 99 | if err := r.Get(context.TODO(), key, &service); err != nil { 100 | return emperror.Wrap(err, "failed to get service") 101 | } 102 | 103 | if func(service *corev1.Service) bool { 104 | timeout := time.After(1 * time.Minute) 105 | ticker := time.NewTicker(500 * time.Millisecond) 106 | for { 107 | select { 108 | case <-timeout: 109 | return false 110 | case <-ticker.C: 111 | r.Get(context.TODO(), key, service) 112 | if k8sutil.GetServiceStatus(service) { 113 | return true 114 | } 115 | } 116 | } 117 | }(&service) { 118 | log.Info("service is available") 119 | } 120 | } 121 | 122 | for _, res := range []resources.ResourceWithLogs{ 123 | r.job, 124 | } { 125 | o := res(log) 126 | err := k8sutil.Reconcile(log, r.Client, o, r.Dast) 127 | if err != nil { 128 | return emperror.WrapWith(err, "failed to reconcile resource", "resource", o.GetObjectKind().GroupVersionKind()) 129 | } 130 | } 131 | 132 | log.V(1).Info("Reconciled") 133 | 134 | return nil 135 | } 136 | -------------------------------------------------------------------------------- /pkg/resources/analyzer/job.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package analyzer 18 | 19 | import ( 20 | "github.com/go-logr/logr" 21 | "istio.io/pkg/log" 22 | batchv1 "k8s.io/api/batch/v1" 23 | corev1 "k8s.io/api/core/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/apimachinery/pkg/runtime/schema" 27 | 28 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 29 | ) 30 | 31 | // job return a job for analyzer 32 | func (r *Reconciler) job(log logr.Logger) runtime.Object { 33 | 34 | return newAnalyzerJob(r.Dast) 35 | } 36 | 37 | func newAnalyzerJob(dast *securityv1alpha1.Dast) *batchv1.Job { 38 | var ownerReferences []metav1.OwnerReference 39 | if dast.Spec.Analyzer.Service != nil { 40 | ownerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(dast.Spec.Analyzer.Service, schema.GroupVersion{Group: "app", Version: "v1"}.WithKind("Service"))} 41 | } else { 42 | ownerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(dast, securityv1alpha1.GroupVersion.WithKind("Dast"))} 43 | } 44 | 45 | annotations := dast.Spec.Analyzer.Service.GetAnnotations() 46 | 47 | command := []string{ 48 | "/dynamic-analyzer", 49 | "scanner", 50 | "-t", 51 | dast.Spec.Analyzer.Target, 52 | "-p", 53 | // TODO use https 54 | "http://" + dast.Spec.ZaProxy.Name + ":8080", 55 | } 56 | 57 | apiScan, ok := annotations["dast.security.banzaicloud.io/apiscan"] 58 | if ok { 59 | if apiScan == "true" { 60 | log.Info("apiscan enabled") 61 | openapiURL, ok := annotations["dast.security.banzaicloud.io/openapi-url"] 62 | if ok { 63 | log.Info("openapi url is defined") 64 | command = []string{ 65 | "/dynamic-analyzer", 66 | "apiscan", 67 | "-t", 68 | dast.Spec.Analyzer.Target, 69 | "-o", 70 | openapiURL, 71 | "-p", 72 | // TODO use https 73 | "http://" + dast.Spec.ZaProxy.Name + ":8080", 74 | } 75 | } else { 76 | log.Info("openapi url is missing") 77 | } 78 | } 79 | } 80 | 81 | backofflimit := int32(5) 82 | completion := int32(1) 83 | return &batchv1.Job{ 84 | ObjectMeta: metav1.ObjectMeta{ 85 | Name: dast.Spec.Analyzer.Name, 86 | Namespace: dast.Namespace, 87 | OwnerReferences: ownerReferences, 88 | }, 89 | Spec: batchv1.JobSpec{ 90 | BackoffLimit: &backofflimit, 91 | Completions: &completion, 92 | Template: corev1.PodTemplateSpec{ 93 | Spec: corev1.PodSpec{ 94 | RestartPolicy: "Never", 95 | Containers: []corev1.Container{ 96 | { 97 | Name: dast.Spec.Analyzer.Name, 98 | Image: dast.Spec.Analyzer.Image, 99 | ImagePullPolicy: "IfNotPresent", 100 | Command: command, 101 | Env: withEnv(dast), 102 | }, 103 | }, 104 | }, 105 | }, 106 | }, 107 | } 108 | } 109 | 110 | func withEnv(dast *securityv1alpha1.Dast) []corev1.EnvVar { 111 | env := []corev1.EnvVar{ 112 | { 113 | Name: "ZAPAPIKEY", 114 | ValueFrom: &corev1.EnvVarSource{ 115 | SecretKeyRef: &corev1.SecretKeySelector{ 116 | LocalObjectReference: corev1.LocalObjectReference{ 117 | Name: dast.Spec.ZaProxy.Name, 118 | }, 119 | Key: "zap_api_key", 120 | }, 121 | }, 122 | }, 123 | } 124 | return env 125 | } 126 | -------------------------------------------------------------------------------- /pkg/resources/reconciler.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package resources 18 | 19 | import ( 20 | "github.com/go-logr/logr" 21 | "k8s.io/apimachinery/pkg/runtime" 22 | "sigs.k8s.io/controller-runtime/pkg/client" 23 | 24 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 25 | ) 26 | 27 | // Reconciler holds client and CR for Dast 28 | type Reconciler struct { 29 | client.Client 30 | Dast *securityv1alpha1.Dast 31 | } 32 | 33 | // ComponentReconciler describes the Reconcile method 34 | type ComponentReconciler interface { 35 | Reconcile(log logr.Logger) error 36 | } 37 | 38 | // ResourceWithLogs function with log parameter 39 | type ResourceWithLogs func(log logr.Logger) runtime.Object 40 | -------------------------------------------------------------------------------- /pkg/resources/zaproxy/deployment.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package zaproxy 18 | 19 | import ( 20 | "github.com/go-logr/logr" 21 | appsv1 "k8s.io/api/apps/v1" 22 | corev1 "k8s.io/api/core/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | "k8s.io/apimachinery/pkg/util/intstr" 26 | 27 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 28 | ) 29 | 30 | // deployment return a deployment for zaproxy 31 | func (r *Reconciler) deployment(log logr.Logger) runtime.Object { 32 | 33 | return newDeployment(r.Dast) 34 | } 35 | 36 | func newDeployment(dast *securityv1alpha1.Dast) *appsv1.Deployment { 37 | labels := map[string]string{ 38 | "app": componentName, 39 | "controller": dast.Name, 40 | } 41 | 42 | var zapImage string 43 | if dast.Spec.ZaProxy.Image == "" { 44 | zapImage = "owasp/zap2docker-live" 45 | } else { 46 | zapImage = dast.Spec.ZaProxy.Image 47 | } 48 | 49 | replicas := int32(1) 50 | return &appsv1.Deployment{ 51 | ObjectMeta: metav1.ObjectMeta{ 52 | Name: dast.Spec.ZaProxy.Name, 53 | Namespace: dast.Namespace, 54 | OwnerReferences: []metav1.OwnerReference{ 55 | *metav1.NewControllerRef(dast, securityv1alpha1.GroupVersion.WithKind("Dast")), 56 | }, 57 | }, 58 | Spec: appsv1.DeploymentSpec{ 59 | Replicas: &replicas, 60 | Selector: &metav1.LabelSelector{ 61 | MatchLabels: labels, 62 | }, 63 | Template: corev1.PodTemplateSpec{ 64 | ObjectMeta: metav1.ObjectMeta{ 65 | Labels: labels, 66 | }, 67 | Spec: corev1.PodSpec{ 68 | Containers: []corev1.Container{ 69 | { 70 | Name: "zap-proxy", 71 | Image: zapImage, 72 | Command: []string{"zap.sh"}, 73 | Args: withArgs(dast.Spec.ZaProxy), 74 | Ports: []corev1.ContainerPort{ 75 | { 76 | Name: "http", 77 | ContainerPort: 8080, 78 | Protocol: "TCP", 79 | }, 80 | }, 81 | ReadinessProbe: &corev1.Probe{ 82 | Handler: corev1.Handler{ 83 | HTTPGet: &corev1.HTTPGetAction{ 84 | Path: "/", 85 | Port: intstr.IntOrString{IntVal: 8080}, 86 | HTTPHeaders: []corev1.HTTPHeader{ 87 | { 88 | Name: "X-ZAP-API-Key", 89 | Value: dast.Spec.ZaProxy.APIKey, 90 | }, 91 | }, 92 | }, 93 | }, 94 | InitialDelaySeconds: 10, 95 | PeriodSeconds: 5, 96 | }, 97 | }, 98 | }, 99 | }, 100 | }, 101 | }, 102 | } 103 | } 104 | 105 | func withArgs(zaProxy securityv1alpha1.ZaProxy) []string { 106 | args := []string{ 107 | "-daemon", 108 | "-host", 109 | "0.0.0.0", 110 | "-port", 111 | "8080", 112 | "-config", 113 | "api.key=" + zaProxy.APIKey, 114 | "-config", 115 | "api.addrs.addr.name=.*", 116 | "-config", 117 | "api.addrs.addr.regex=true", 118 | } 119 | 120 | if zaProxy.Config != nil { 121 | for _, config := range zaProxy.Config { 122 | args = append(args, []string{"-config", config}...) 123 | } 124 | } 125 | 126 | return args 127 | } 128 | -------------------------------------------------------------------------------- /pkg/resources/zaproxy/secret.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package zaproxy 18 | 19 | import ( 20 | "github.com/go-logr/logr" 21 | corev1 "k8s.io/api/core/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | 25 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 26 | ) 27 | 28 | // service return a secret for zaproxy 29 | func (r *Reconciler) secret(log logr.Logger) runtime.Object { 30 | 31 | return newSecret(r.Dast) 32 | } 33 | 34 | func newSecret(dast *securityv1alpha1.Dast) *corev1.Secret { 35 | return &corev1.Secret{ 36 | ObjectMeta: metav1.ObjectMeta{ 37 | Name: dast.Spec.ZaProxy.Name, 38 | Namespace: dast.Namespace, 39 | OwnerReferences: []metav1.OwnerReference{ 40 | *metav1.NewControllerRef(dast, securityv1alpha1.GroupVersion.WithKind("Dast")), 41 | }, 42 | }, 43 | Data: map[string][]byte{ 44 | "zap_api_key": []byte(dast.Spec.ZaProxy.APIKey), 45 | }, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/resources/zaproxy/service.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package zaproxy 18 | 19 | import ( 20 | "github.com/go-logr/logr" 21 | corev1 "k8s.io/api/core/v1" 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/apimachinery/pkg/util/intstr" 25 | 26 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 27 | ) 28 | 29 | // service return a service for zaproxy 30 | func (r *Reconciler) service(log logr.Logger) runtime.Object { 31 | 32 | return newService(r.Dast) 33 | } 34 | 35 | func newService(dast *securityv1alpha1.Dast) *corev1.Service { 36 | labels := map[string]string{ 37 | "app": componentName, 38 | "controller": dast.Name, 39 | } 40 | return &corev1.Service{ 41 | ObjectMeta: metav1.ObjectMeta{ 42 | Name: dast.Spec.ZaProxy.Name, 43 | Namespace: dast.Namespace, 44 | OwnerReferences: []metav1.OwnerReference{ 45 | *metav1.NewControllerRef(dast, securityv1alpha1.GroupVersion.WithKind("Dast")), 46 | }, 47 | }, 48 | Spec: corev1.ServiceSpec{ 49 | Selector: labels, 50 | Ports: []corev1.ServicePort{ 51 | { 52 | Name: "http", 53 | Protocol: "TCP", 54 | Port: 8080, 55 | TargetPort: intstr.IntOrString{IntVal: 8080}, 56 | }, 57 | }, 58 | }, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/resources/zaproxy/zaproxy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package zaproxy 18 | 19 | import ( 20 | "emperror.dev/emperror" 21 | "github.com/go-logr/logr" 22 | "sigs.k8s.io/controller-runtime/pkg/client" 23 | 24 | securityv1alpha1 "github.com/banzaicloud/dast-operator/api/v1alpha1" 25 | "github.com/banzaicloud/dast-operator/pkg/k8sutil" 26 | "github.com/banzaicloud/dast-operator/pkg/resources" 27 | ) 28 | 29 | const ( 30 | componentName = "zaproxy" 31 | ) 32 | 33 | var labelSelector = map[string]string{ 34 | "app": "zaproxy", 35 | } 36 | 37 | // Reconciler implements the Component Reconciler 38 | type Reconciler struct { 39 | resources.Reconciler 40 | } 41 | 42 | // New creates a new reconciler for Zaproxy 43 | func New(client client.Client, dast *securityv1alpha1.Dast) *Reconciler { 44 | return &Reconciler{ 45 | Reconciler: resources.Reconciler{ 46 | Client: client, 47 | Dast: dast, 48 | }, 49 | } 50 | } 51 | 52 | // Reconcile implements the reconcile logic for Zaproxy 53 | func (r *Reconciler) Reconcile(log logr.Logger) error { 54 | log = log.WithValues("component", componentName) 55 | 56 | log.V(1).Info("Reconciling") 57 | 58 | for _, res := range []resources.ResourceWithLogs{ 59 | r.secret, 60 | r.deployment, 61 | r.service, 62 | } { 63 | o := res(log) 64 | err := k8sutil.Reconcile(log, r.Client, o, r.Dast) 65 | if err != nil { 66 | return emperror.WrapWith(err, "failed to reconcile resource", "resource", o.GetObjectKind().GroupVersionKind()) 67 | } 68 | } 69 | 70 | log.V(1).Info("Reconciled") 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /webhooks/ingress_validator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Banzai Cloud. 3 | 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 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package webhooks 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net/http" 23 | "strconv" 24 | 25 | "emperror.dev/emperror" 26 | "github.com/banzaicloud/dast-operator/pkg/k8sutil" 27 | "github.com/go-logr/logr" 28 | "github.com/spf13/cast" 29 | "github.com/zaproxy/zap-api-go/zap" 30 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 33 | ) 34 | 35 | // +kubebuilder:webhook:path=/ingress,mutating=false,failurePolicy=fail,groups="extensions";"networking.k8s.io",resources=ingresses,verbs=create,versions=v1beta1;v1,name=dast.security.banzaicloud.io 36 | 37 | // NewIngressValidator creates new ingressValidator 38 | func NewIngressValidator(client client.Client, log logr.Logger) IngressValidator { 39 | return &ingressValidator{ 40 | Client: client, 41 | Log: log, 42 | } 43 | } 44 | 45 | // IngressValidator implements Handle 46 | type IngressValidator interface { 47 | Handle(context.Context, admission.Request) admission.Response 48 | } 49 | 50 | type ingressValidator struct { 51 | Client client.Client 52 | decoder *admission.Decoder 53 | Log logr.Logger 54 | } 55 | 56 | // ingressValidator validates ingress. 57 | func (a *ingressValidator) Handle(ctx context.Context, req admission.Request) admission.Response { 58 | ingress := &unstructured.Unstructured{} 59 | 60 | err := a.decoder.Decode(req, ingress) 61 | if err != nil { 62 | return admission.Errored(http.StatusBadRequest, err) 63 | } 64 | 65 | tresholds := getIngressTresholds(ingress) 66 | 67 | backendServices, err := k8sutil.GetIngressBackendServices(ingress, a.Log) 68 | if err != nil { 69 | return admission.Errored(http.StatusNotImplemented, err) 70 | } 71 | a.Log.Info("Services", "backend_services", backendServices) 72 | ok, err := checkServices(backendServices, ingress.GetNamespace(), a.Log, a.Client, tresholds) 73 | if err != nil { 74 | return admission.Errored(http.StatusInternalServerError, err) 75 | } 76 | if !ok { 77 | return admission.Denied("scan results are above treshold") 78 | } 79 | 80 | return admission.Allowed("scan results are below treshold") 81 | 82 | } 83 | 84 | // InjectDecoder injects the decoder. 85 | func (a *ingressValidator) InjectDecoder(d *admission.Decoder) error { 86 | a.decoder = d 87 | return nil 88 | } 89 | 90 | func checkServices(services []map[string]string, namespace string, log logr.Logger, client client.Client, tresholds map[string]int) (bool, error) { 91 | for _, service := range services { 92 | k8sService, err := k8sutil.GetServiceByName(service["name"], namespace, client) 93 | if err != nil { 94 | return false, err 95 | } 96 | zaProxyCfg, err := k8sutil.GetServiceAnotations(k8sService, log) 97 | if err != nil { 98 | return false, err 99 | } 100 | secret, err := k8sutil.GetSercretByName(zaProxyCfg["name"], zaProxyCfg["namespace"], client, log) 101 | if err != nil { 102 | return false, err 103 | } 104 | 105 | // TODO check scan status and wait for end of progress 106 | // check the scanner job is running, completed or not exist 107 | 108 | zapCore, err := newZapClient(zaProxyCfg["name"], zaProxyCfg["namespace"], string(secret.Data["zap_api_key"]), log) 109 | if err != nil { 110 | return false, err 111 | } 112 | summary, err := getServiceScanSummary(service, namespace, zapCore, log) 113 | if err != nil { 114 | return false, err 115 | } 116 | 117 | s, err := cast.ToStringMapIntE(summary["alertsSummary"]) 118 | if err != nil { 119 | return false, err 120 | } 121 | for key, value := range s { 122 | if value > tresholds[key] { 123 | return false, nil 124 | } 125 | } 126 | } 127 | return true, nil 128 | } 129 | 130 | func getIngressTresholds(ingress *unstructured.Unstructured) map[string]int { 131 | annotations := ingress.GetAnnotations() 132 | treshold := map[string]int{ 133 | "High": 0, 134 | "Medium": 0, 135 | "Low": 0, 136 | "Informational": 0, 137 | } 138 | if high, ok := annotations["dast.security.banzaicloud.io/high"]; ok { 139 | treshold["High"], _ = strconv.Atoi(high) 140 | } 141 | if medium, ok := annotations["dast.security.banzaicloud.io/medium"]; ok { 142 | treshold["Medium"], _ = strconv.Atoi(medium) 143 | } 144 | if low, ok := annotations["dast.security.banzaicloud.io/low"]; ok { 145 | treshold["Low"], _ = strconv.Atoi(low) 146 | } 147 | if informational, ok := annotations["dast.security.banzaicloud.io/informational"]; ok { 148 | treshold["Informational"], _ = strconv.Atoi(informational) 149 | } 150 | return treshold 151 | } 152 | 153 | // TODO refactor to pkg 154 | func getServiceScanSummary(service map[string]string, namespace string, zapCore *zap.Core, log logr.Logger) (map[string]interface{}, error) { 155 | target := fmt.Sprintf("http://%s.%s.svc.cluster.local:%s", service["name"], namespace, service["port"]) 156 | log.Info("Target", "url", target) 157 | summary, err := zapCore.AlertsSummary(target) 158 | if err != nil { 159 | return nil, emperror.Wrap(err, "failed to get service summary from ZaProxy") 160 | } 161 | log.Info("Tresholds", "summary", summary) 162 | return summary, nil 163 | } 164 | 165 | func newZapClient(zapAddr, zapNamespace, apiKey string, log logr.Logger) (*zap.Core, error) { 166 | // TODO use https 167 | cfg := &zap.Config{ 168 | Proxy: "http://" + zapAddr + "." + zapNamespace + ".svc.cluster.local:8080", 169 | APIKey: apiKey, 170 | } 171 | client, err := zap.NewClient(cfg) 172 | if err != nil { 173 | return nil, emperror.Wrap(err, "failed to create zap interface") 174 | } 175 | return client.Core(), nil 176 | } 177 | --------------------------------------------------------------------------------