├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── actions │ └── repo_access │ │ └── action.yaml ├── dependabot.yaml └── workflows │ ├── govulncheck.yaml │ ├── make-self-upgrade.yaml │ ├── release.yml │ └── tests.yaml ├── .gitignore ├── .golangci.yaml ├── LICENSE ├── LICENSES ├── Makefile ├── OWNERS ├── OWNERS_ALIASES ├── README.md ├── agent.yaml ├── api ├── agent.go ├── cluster.go ├── cluster_test.go ├── common.go ├── datareading.go ├── datareading_test.go └── report.go ├── cmd ├── agent.go ├── agent_test.go ├── echo.go ├── helpers.go ├── root.go ├── testdata │ └── agent │ │ └── one-shot │ │ └── success │ │ ├── config.yaml │ │ ├── input.json │ │ └── kubeconfig.yaml └── version.go ├── deploy └── charts │ ├── jetstack-agent │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── README.md.gotmpl │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── configmap.yaml │ │ ├── deployment.yaml │ │ ├── rbac.yaml │ │ ├── secret.yaml │ │ └── serviceaccount.yaml │ ├── tests │ │ ├── __snapshot__ │ │ │ └── configuration_test.yaml.snap │ │ ├── configuration_test.yaml │ │ ├── deployment_test.yaml │ │ └── values │ │ │ ├── custom-config.yaml │ │ │ └── custom-volumes.yaml │ └── values.yaml │ └── venafi-kubernetes-agent │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── crd_bases │ ├── crd.footer.yaml │ ├── crd.header-without-validations.yaml │ ├── crd.header.yaml │ └── jetstack.io_venaficonnections.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── _venafi-connection.tpl │ ├── configmap.yaml │ ├── deployment.yaml │ ├── poddisruptionbudget.yaml │ ├── podmonitor.yaml │ ├── rbac.yaml │ ├── serviceaccount.yaml │ ├── venafi-connection-crd.without-validations.yaml │ ├── venafi-connection-crd.yaml │ ├── venafi-connection-rbac.yaml │ └── venafi-rbac.yaml │ ├── tests │ ├── deployment_test.yaml │ └── values │ │ └── custom-volumes.yaml │ ├── values.linter.exceptions │ ├── values.schema.json │ └── values.yaml ├── docs ├── datagatherers │ ├── aks.md │ ├── eks.md │ ├── gke.md │ ├── k8s-discovery.md │ ├── k8s-dynamic.md │ └── local.md ├── guides │ ├── cosign │ │ └── guide.md │ └── gke_local │ │ ├── guide.md │ │ └── img │ │ ├── cluster.png │ │ ├── ready.png │ │ ├── release.png │ │ └── select_token.png └── images │ ├── azblob_keys.png │ ├── js.png │ └── preflight_package.png ├── examples ├── cert-manager-agent.yaml ├── echo │ ├── example.json │ └── example2.json └── one-shot-secret.yaml ├── go.mod ├── go.sum ├── hack └── e2e │ ├── application-team-1.yaml │ ├── test.sh │ ├── values.venafi-kubernetes-agent.yaml │ └── venafi-components.yaml ├── klone.yaml ├── main.go ├── make ├── 00_mod.mk ├── 02_mod.mk ├── _shared │ ├── generate-verify │ │ ├── 00_mod.mk │ │ ├── 02_mod.mk │ │ └── util │ │ │ └── verify.sh │ ├── go │ │ ├── .golangci.override.yaml │ │ ├── 01_mod.mk │ │ ├── README.md │ │ └── base │ │ │ └── .github │ │ │ └── workflows │ │ │ └── govulncheck.yaml │ ├── helm │ │ ├── 01_mod.mk │ │ ├── crd.template.footer.yaml │ │ ├── crd.template.header.yaml │ │ ├── crds.mk │ │ ├── crds_dir.README.md │ │ ├── deploy.mk │ │ └── helm.mk │ ├── help │ │ ├── 01_mod.mk │ │ └── help.sh │ ├── kind │ │ ├── 00_kind_image_versions.mk │ │ ├── 00_mod.mk │ │ ├── 01_mod.mk │ │ ├── kind-image-preload.mk │ │ └── kind.mk │ ├── klone │ │ └── 01_mod.mk │ ├── licenses │ │ ├── 00_mod.mk │ │ └── 01_mod.mk │ ├── oci-build │ │ ├── 00_mod.mk │ │ └── 01_mod.mk │ ├── oci-publish │ │ ├── 00_mod.mk │ │ ├── 01_mod.mk │ │ └── image-exists.sh │ ├── repository-base │ │ ├── 01_mod.mk │ │ ├── base-dependabot │ │ │ └── .github │ │ │ │ └── dependabot.yaml │ │ └── base │ │ │ ├── .github │ │ │ └── workflows │ │ │ │ └── make-self-upgrade.yaml │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ └── OWNERS_ALIASES │ └── tools │ │ ├── 00_mod.mk │ │ └── util │ │ ├── checkhash.sh │ │ ├── hash.sh │ │ └── lock.sh ├── connection_crd │ └── main.go └── test-unit.mk └── pkg ├── agent ├── config.go ├── config_test.go ├── dummy_data_gatherer.go ├── metrics.go └── run.go ├── client ├── client.go ├── client_api_token.go ├── client_oauth.go ├── client_venafi_cloud.go ├── client_venconn.go └── client_venconn_test.go ├── datagatherer ├── datagatherer.go ├── k8s │ ├── cache.go │ ├── cache_test.go │ ├── client.go │ ├── client_test.go │ ├── discovery.go │ ├── dynamic.go │ ├── dynamic_test.go │ ├── fieldfilter.go │ └── fieldfilter_test.go └── local │ └── local.go ├── echo ├── echo.go └── echo_test.go ├── internal └── cyberark │ └── servicediscovery │ ├── discovery.go │ ├── discovery_test.go │ └── testdata │ ├── README.md │ └── discovery_success.json ├── kubeconfig └── kubeconfig.go ├── logs ├── logs.go └── logs_test.go ├── permissions ├── generate.go └── generate_test.go ├── testutil ├── envtest.go ├── undent.go └── undent_test.go └── version └── version.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Issue for something that isn't working as expected 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **What happened?** 13 | 14 | What is the current bug behavior? 15 | Give all the context you can, provide relevant logs and/or screenshots. 16 | 17 | **What should had happened?** 18 | 19 | Describe what you expected to happen. 20 | 21 | **Possible fixes** 22 | 23 | This section is optional and should include possible solutions to explore and discuss further. 24 | -------------------------------------------------------------------------------- /.github/actions/repo_access/action.yaml: -------------------------------------------------------------------------------- 1 | name: 'Setup repo access' 2 | description: 'Setups authenticate to GitHub repos' 3 | inputs: 4 | DEPLOY_KEY_READ_VENAFI_CONNECTION_LIB: 5 | required: true 6 | description: "DEPLOY_KEY_READ_VENAFI_CONNECTION_LIB secret" 7 | outputs: {} 8 | runs: 9 | using: "composite" 10 | steps: 11 | - name: Configure jetstack/venafi-connection-lib repo pull access 12 | shell: bash 13 | run: | 14 | mkdir ~/.ssh 15 | chmod 700 ~/.ssh 16 | 17 | echo "${{ inputs.DEPLOY_KEY_READ_VENAFI_CONNECTION_LIB }}" > ~/.ssh/venafi_connection_lib_id 18 | chmod 600 ~/.ssh/venafi_connection_lib_id 19 | 20 | cat <> ~/.ssh/config 21 | Host venafi-connection-lib.github.com 22 | HostName github.com 23 | IdentityFile ~/.ssh/venafi_connection_lib_id 24 | IdentitiesOnly yes 25 | EOT 26 | 27 | cat <> ~/.gitconfig 28 | [url "git@venafi-connection-lib.github.com:jetstack/venafi-connection-lib"] 29 | insteadOf = https://github.com/jetstack/venafi-connection-lib 30 | EOT 31 | 32 | echo "GOPRIVATE=github.com/jetstack/venafi-connection-lib" >> $GITHUB_ENV 33 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 2 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base-dependabot/.github/dependabot.yaml instead. 3 | 4 | # Update Go dependencies and GitHub Actions dependencies daily. 5 | version: 2 6 | updates: 7 | - package-ecosystem: gomod 8 | directory: / 9 | schedule: 10 | interval: daily 11 | groups: 12 | all: 13 | patterns: ["*"] 14 | - package-ecosystem: github-actions 15 | directory: / 16 | schedule: 17 | interval: daily 18 | groups: 19 | all: 20 | patterns: ["*"] 21 | -------------------------------------------------------------------------------- /.github/workflows/govulncheck.yaml: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 2 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/go/base/.github/workflows/govulncheck.yaml instead. 3 | 4 | # Run govulncheck at midnight every night on the main branch, 5 | # to alert us to recent vulnerabilities which affect the Go code in this 6 | # project. 7 | name: govulncheck 8 | on: 9 | workflow_dispatch: {} 10 | schedule: 11 | - cron: '0 0 * * *' 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | govulncheck: 18 | runs-on: ubuntu-latest 19 | 20 | if: github.repository_owner == 'cert-manager' 21 | 22 | steps: 23 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 24 | # Adding `fetch-depth: 0` makes sure tags are also fetched. We need 25 | # the tags so `git describe` returns a valid version. 26 | # see https://github.com/actions/checkout/issues/701 for extra info about this option 27 | with: { fetch-depth: 0 } 28 | 29 | - id: go-version 30 | run: | 31 | make print-go-version >> "$GITHUB_OUTPUT" 32 | 33 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 34 | with: 35 | go-version: ${{ steps.go-version.outputs.result }} 36 | 37 | - run: make verify-govulncheck 38 | -------------------------------------------------------------------------------- /.github/workflows/make-self-upgrade.yaml: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 2 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/.github/workflows/make-self-upgrade.yaml instead. 3 | 4 | name: make-self-upgrade 5 | concurrency: make-self-upgrade 6 | on: 7 | workflow_dispatch: {} 8 | schedule: 9 | - cron: '0 0 * * *' 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | self_upgrade: 16 | runs-on: ubuntu-latest 17 | 18 | if: github.repository_owner == 'cert-manager' 19 | 20 | permissions: 21 | contents: write 22 | pull-requests: write 23 | 24 | env: 25 | SOURCE_BRANCH: "${{ github.ref_name }}" 26 | SELF_UPGRADE_BRANCH: "self-upgrade-${{ github.ref_name }}" 27 | 28 | steps: 29 | - name: Fail if branch is not head of branch. 30 | if: ${{ !startsWith(github.ref, 'refs/heads/') && env.SOURCE_BRANCH != '' && env.SELF_UPGRADE_BRANCH != '' }} 31 | run: | 32 | echo "This workflow should not be run on a non-branch-head." 33 | exit 1 34 | 35 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 36 | # Adding `fetch-depth: 0` makes sure tags are also fetched. We need 37 | # the tags so `git describe` returns a valid version. 38 | # see https://github.com/actions/checkout/issues/701 for extra info about this option 39 | with: { fetch-depth: 0 } 40 | 41 | - id: go-version 42 | run: | 43 | make print-go-version >> "$GITHUB_OUTPUT" 44 | 45 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 46 | with: 47 | go-version: ${{ steps.go-version.outputs.result }} 48 | 49 | - run: | 50 | git checkout -B "$SELF_UPGRADE_BRANCH" 51 | 52 | - run: | 53 | make -j upgrade-klone 54 | make -j generate 55 | 56 | - id: is-up-to-date 57 | shell: bash 58 | run: | 59 | git_status=$(git status -s) 60 | is_up_to_date="true" 61 | if [ -n "$git_status" ]; then 62 | is_up_to_date="false" 63 | echo "The following changes will be committed:" 64 | echo "$git_status" 65 | fi 66 | echo "result=$is_up_to_date" >> "$GITHUB_OUTPUT" 67 | 68 | - if: ${{ steps.is-up-to-date.outputs.result != 'true' }} 69 | run: | 70 | git config --global user.name "cert-manager-bot" 71 | git config --global user.email "cert-manager-bot@users.noreply.github.com" 72 | git add -A && git commit -m "BOT: run 'make upgrade-klone' and 'make generate'" --signoff 73 | git push -f origin "$SELF_UPGRADE_BRANCH" 74 | 75 | - if: ${{ steps.is-up-to-date.outputs.result != 'true' }} 76 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 77 | with: 78 | script: | 79 | const { repo, owner } = context.repo; 80 | const pulls = await github.rest.pulls.list({ 81 | owner: owner, 82 | repo: repo, 83 | head: owner + ':' + process.env.SELF_UPGRADE_BRANCH, 84 | base: process.env.SOURCE_BRANCH, 85 | state: 'open', 86 | }); 87 | 88 | if (pulls.data.length < 1) { 89 | const result = await github.rest.pulls.create({ 90 | title: '[CI] Merge ' + process.env.SELF_UPGRADE_BRANCH + ' into ' + process.env.SOURCE_BRANCH, 91 | owner: owner, 92 | repo: repo, 93 | head: process.env.SELF_UPGRADE_BRANCH, 94 | base: process.env.SOURCE_BRANCH, 95 | body: [ 96 | 'This PR is auto-generated to bump the Makefile modules.', 97 | ].join('\n'), 98 | }); 99 | await github.rest.issues.addLabels({ 100 | owner, 101 | repo, 102 | issue_number: result.data.number, 103 | labels: ['skip-review'] 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | env: 8 | VERSION: ${{ github.ref_name }} 9 | 10 | jobs: 11 | build_and_push: 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | contents: read # needed for checkout 16 | id-token: write # needed for keyless signing & google auth 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: ./.github/actions/repo_access 22 | with: 23 | DEPLOY_KEY_READ_VENAFI_CONNECTION_LIB: ${{ secrets.DEPLOY_KEY_READ_VENAFI_CONNECTION_LIB }} 24 | 25 | - id: go-version 26 | run: | 27 | make print-go-version >> "$GITHUB_OUTPUT" 28 | 29 | - uses: docker/login-action@v3 30 | with: 31 | registry: quay.io 32 | username: ${{ secrets.QUAY_USERNAME }} 33 | password: ${{ secrets.QUAY_PASSWORD }} 34 | 35 | - uses: actions/setup-go@v5 36 | with: 37 | go-version: ${{ steps.go-version.outputs.result }} 38 | 39 | - id: release 40 | run: make release 41 | 42 | outputs: 43 | RELEASE_OCI_PREFLIGHT_IMAGE: ${{ steps.release.outputs.RELEASE_OCI_PREFLIGHT_IMAGE }} 44 | RELEASE_OCI_PREFLIGHT_TAG: ${{ steps.release.outputs.RELEASE_OCI_PREFLIGHT_TAG }} 45 | RELEASE_HELM_CHART_IMAGE: ${{ steps.release.outputs.RELEASE_HELM_CHART_IMAGE }} 46 | RELEASE_HELM_CHART_VERSION: ${{ steps.release.outputs.RELEASE_HELM_CHART_VERSION }} 47 | 48 | github_release: 49 | runs-on: ubuntu-latest 50 | 51 | needs: build_and_push 52 | 53 | permissions: 54 | contents: write # needed for creating a PR 55 | pull-requests: write # needed for creating a PR 56 | 57 | steps: 58 | - run: | 59 | touch .notes-file 60 | echo "OCI_PREFLIGHT_IMAGE: ${{ needs.build_and_push.outputs.RELEASE_OCI_PREFLIGHT_IMAGE }}" >> .notes-file 61 | echo "OCI_PREFLIGHT_TAG: ${{ needs.build_and_push.outputs.RELEASE_OCI_PREFLIGHT_TAG }}" >> .notes-file 62 | echo "HELM_CHART_IMAGE: ${{ needs.build_and_push.outputs.RELEASE_HELM_CHART_IMAGE }}" >> .notes-file 63 | echo "HELM_CHART_VERSION: ${{ needs.build_and_push.outputs.RELEASE_HELM_CHART_VERSION }}" >> .notes-file 64 | 65 | - env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | run: | 68 | gh release create "$VERSION" \ 69 | --repo="$GITHUB_REPOSITORY" \ 70 | --title="${VERSION}" \ 71 | --draft \ 72 | --verify-tag \ 73 | --notes-file .notes-file 74 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: {} 6 | jobs: 7 | verify: 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 15 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | 16 | - uses: ./.github/actions/repo_access 17 | with: 18 | DEPLOY_KEY_READ_VENAFI_CONNECTION_LIB: ${{ secrets.DEPLOY_KEY_READ_VENAFI_CONNECTION_LIB }} 19 | 20 | - id: go-version 21 | run: | 22 | make print-go-version >> "$GITHUB_OUTPUT" 23 | 24 | - uses: actions/setup-go@v5 25 | with: 26 | go-version: ${{ steps.go-version.outputs.result }} 27 | 28 | - uses: actions/cache@v4 29 | with: 30 | path: _bin/downloaded 31 | key: downloaded-${{ runner.os }}-${{ hashFiles('klone.yaml') }}-verify 32 | 33 | - run: make -j verify 34 | 35 | test-unit: 36 | runs-on: ubuntu-latest 37 | timeout-minutes: 15 38 | 39 | permissions: 40 | contents: read # needed for checkout 41 | id-token: write # needed for google auth 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | with: 46 | fetch-depth: 0 47 | 48 | - uses: ./.github/actions/repo_access 49 | with: 50 | DEPLOY_KEY_READ_VENAFI_CONNECTION_LIB: ${{ secrets.DEPLOY_KEY_READ_VENAFI_CONNECTION_LIB }} 51 | 52 | - id: go-version 53 | run: | 54 | make print-go-version >> "$GITHUB_OUTPUT" 55 | 56 | - uses: actions/setup-go@v5 57 | with: 58 | go-version: ${{ steps.go-version.outputs.result }} 59 | 60 | - uses: actions/cache@v4 61 | with: 62 | path: _bin/downloaded 63 | key: downloaded-${{ runner.os }}-${{ hashFiles('klone.yaml') }}-test-unit 64 | 65 | - run: make -j test-unit 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /preflight 2 | /preflight.yaml 3 | /builds 4 | /bundles 5 | /output 6 | credentials.json 7 | .terraform 8 | terraform.tfstate 9 | terraform.tfstate.backup 10 | bom.xml 11 | predicate.json 12 | *.pem 13 | *.pub 14 | *.tgz 15 | 16 | _bin 17 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | exclusions: 5 | generated: lax 6 | presets: [comments, common-false-positives, legacy, std-error-handling] 7 | rules: 8 | - linters: 9 | - bodyclose 10 | - dupword 11 | - errcheck 12 | - errchkjson 13 | - forbidigo 14 | - gocritic 15 | - gosec 16 | - govet 17 | - misspell 18 | - musttag 19 | - nilerr 20 | - noctx 21 | - predeclared 22 | - staticcheck 23 | - unconvert 24 | - unparam 25 | - usestdlibvars 26 | text: .* 27 | paths: [third_party$, builtin$, examples$] 28 | warn-unused: true 29 | settings: 30 | staticcheck: 31 | checks: ["all", "-ST1000", "-ST1001", "-ST1003", "-ST1005", "-ST1012", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1001", "-QF1003", "-QF1008"] 32 | enable: 33 | - asasalint 34 | - asciicheck 35 | - bidichk 36 | - bodyclose 37 | - canonicalheader 38 | - contextcheck 39 | - copyloopvar 40 | - decorder 41 | - dogsled 42 | - dupword 43 | - durationcheck 44 | - errcheck 45 | - errchkjson 46 | - errname 47 | - exhaustive 48 | - exptostd 49 | - forbidigo 50 | - ginkgolinter 51 | - gocheckcompilerdirectives 52 | - gochecksumtype 53 | - gocritic 54 | - goheader 55 | - goprintffuncname 56 | - gosec 57 | - gosmopolitan 58 | - govet 59 | - grouper 60 | - importas 61 | - ineffassign 62 | - interfacebloat 63 | - intrange 64 | - loggercheck 65 | - makezero 66 | - mirror 67 | - misspell 68 | - musttag 69 | - nakedret 70 | - nilerr 71 | - nilnil 72 | - noctx 73 | - nosprintfhostport 74 | - predeclared 75 | - promlinter 76 | - protogetter 77 | - reassign 78 | - sloglint 79 | - staticcheck 80 | - tagalign 81 | - testableexamples 82 | - unconvert 83 | - unparam 84 | - unused 85 | - usestdlibvars 86 | - usetesting 87 | - wastedassign 88 | formatters: 89 | enable: [gci, gofmt] 90 | settings: 91 | gci: 92 | sections: 93 | - standard # Standard section: captures all standard packages. 94 | - default # Default section: contains all imports that could not be matched to another section type. 95 | - prefix(github.com/jetstack/preflight) # Custom section: groups all imports with the specified Prefix. 96 | - blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled. 97 | - dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled. 98 | exclusions: 99 | generated: lax 100 | paths: [third_party$, builtin$, examples$] 101 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 16 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/Makefile instead. 17 | 18 | # NOTE FOR DEVELOPERS: "How do the Makefiles work and how can I extend them?" 19 | # 20 | # Shared Makefile logic lives in the make/_shared/ directory. The source of truth for these files 21 | # lies outside of this repository, eg. in the cert-manager/makefile-modules repository. 22 | # 23 | # Logic specific to this repository must be defined in the make/00_mod.mk and make/02_mod.mk files: 24 | # - The make/00_mod.mk file is included first and contains variable definitions needed by 25 | # the shared Makefile logic. 26 | # - The make/02_mod.mk file is included later, it can make use of most of the shared targets 27 | # defined in the make/_shared/ directory (all targets defined in 00_mod.mk and 01_mod.mk). 28 | # This file should be used to define targets specific to this repository. 29 | 30 | ################################## 31 | 32 | # Some modules build their dependencies from variables, we want these to be 33 | # evaluated at the last possible moment. For this we use second expansion to 34 | # re-evaluate the generate and verify targets a second time. 35 | # 36 | # See https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html 37 | .SECONDEXPANSION: 38 | 39 | # For details on some of these "prelude" settings, see: 40 | # https://clarkgrubb.com/makefile-style-guide 41 | MAKEFLAGS += --warn-undefined-variables --no-builtin-rules 42 | SHELL := /usr/bin/env bash 43 | .SHELLFLAGS := -uo pipefail -c 44 | .DEFAULT_GOAL := help 45 | .DELETE_ON_ERROR: 46 | .SUFFIXES: 47 | FORCE: 48 | 49 | noop: # do nothing 50 | 51 | # Set empty value for MAKECMDGOALS to prevent the "warning: undefined variable 'MAKECMDGOALS'" 52 | # warning from happening when running make without arguments 53 | MAKECMDGOALS ?= 54 | 55 | ################################## 56 | # Host OS and architecture setup # 57 | ################################## 58 | 59 | # The reason we don't use "go env GOOS" or "go env GOARCH" is that the "go" 60 | # binary may not be available in the PATH yet when the Makefiles are 61 | # evaluated. HOST_OS and HOST_ARCH only support Linux, *BSD and macOS (M1 62 | # and Intel). 63 | host_os := $(shell uname -s | tr A-Z a-z) 64 | host_arch := $(shell uname -m) 65 | HOST_OS ?= $(host_os) 66 | HOST_ARCH ?= $(host_arch) 67 | 68 | ifeq (x86_64, $(HOST_ARCH)) 69 | HOST_ARCH = amd64 70 | else ifeq (aarch64, $(HOST_ARCH)) 71 | # linux reports the arm64 arch as aarch64 72 | HOST_ARCH = arm64 73 | endif 74 | 75 | ################################## 76 | # Git and versioning information # 77 | ################################## 78 | 79 | git_version := $(shell git describe --tags --always --match='v*' --abbrev=14 --dirty) 80 | VERSION ?= $(git_version) 81 | IS_PRERELEASE := $(shell git describe --tags --always --match='v*' --abbrev=0 | grep -q '-' && echo true || echo false) 82 | GITCOMMIT := $(shell git rev-parse HEAD) 83 | GITEPOCH := $(shell git show -s --format=%ct HEAD) 84 | 85 | ################################## 86 | # Global variables and dirs # 87 | ################################## 88 | 89 | bin_dir := _bin 90 | 91 | # The ARTIFACTS environment variable is set by the CI system to a directory 92 | # where artifacts should be placed. These artifacts are then uploaded to a 93 | # storage bucket by the CI system (https://docs.prow.k8s.io/docs/components/pod-utilities/). 94 | # An example of such an artifact is a jUnit XML file containing test results. 95 | # If the ARTIFACTS environment variable is not set, we default to a local 96 | # directory in the _bin directory. 97 | ARTIFACTS ?= $(bin_dir)/artifacts 98 | 99 | $(bin_dir) $(ARTIFACTS) $(bin_dir)/scratch: 100 | mkdir -p $@ 101 | 102 | .PHONY: clean 103 | ## Clean all temporary files 104 | ## @category [shared] Tools 105 | clean: 106 | rm -rf $(bin_dir) 107 | 108 | ################################## 109 | # Include all the Makefiles # 110 | ################################## 111 | 112 | -include make/00_mod.mk 113 | -include make/_shared/*/00_mod.mk 114 | -include make/_shared/*/01_mod.mk 115 | -include make/02_mod.mk 116 | -include make/_shared/*/02_mod.mk 117 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - j-fuentes 3 | - wwwil 4 | - charlieegan3 5 | - akvilemar 6 | - james-w 7 | - tfadeyi 8 | reviewers: 9 | - j-fuentes 10 | - wwwil 11 | - charlieegan3 12 | -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 2 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/OWNERS_ALIASES instead. 3 | 4 | aliases: 5 | cm-maintainers: 6 | - munnerz 7 | - joshvanl 8 | - wallrj 9 | - jakexks 10 | - maelvls 11 | - sgtcodfish 12 | - inteon 13 | - thatsmrtalbot 14 | - erikgb 15 | -------------------------------------------------------------------------------- /agent.yaml: -------------------------------------------------------------------------------- 1 | server: "https://platform.jetstack.io" 2 | organization_id: "my-organization" 3 | cluster_id: "my_cluster" 4 | period: "0h1m0s" 5 | data-gatherers: 6 | - kind: "dummy" 7 | name: "dummy" 8 | config: 9 | failed-attempts: 5 10 | - kind: "dummy" 11 | name: "dummy-fail" 12 | config: 13 | always-fail: true 14 | venafi-cloud: 15 | uploader_id: "example-id" 16 | upload_path: "/example/endpoint/path" 17 | -------------------------------------------------------------------------------- /api/agent.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // AgentMetadata is metadata about the agent. 4 | type AgentMetadata struct { 5 | Version string `json:"version"` 6 | // ClusterID is the name of the cluster or host where the agent is running. 7 | // It may send data for other clusters in its datareadings. 8 | ClusterID string `json:"cluster_id"` 9 | } 10 | -------------------------------------------------------------------------------- /api/cluster.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "encoding/json" 4 | 5 | // ClusterSummary contains a summary of the most recent status of a cluster. 6 | type ClusterSummary struct { 7 | Cluster string `json:"cluster"` 8 | LatestReportSet *ReportSet `json:"latestReportSet"` 9 | } 10 | 11 | // ReportSet groups one or more reports of different packages with the same timestamp for the same cluster. 12 | type ReportSet struct { 13 | Cluster string `json:"-"` 14 | Timestamp Time `json:"timestamp"` 15 | FailureCount int `json:"failureCount"` 16 | SuccessCount int `json:"successCount"` 17 | Reports []*ReportSummary `json:"reports"` 18 | } 19 | 20 | // ReportSummary constains a summary of a report. 21 | type ReportSummary struct { 22 | ID string `json:"id"` 23 | Package string `json:"package"` 24 | Cluster string `json:"-"` 25 | Timestamp Time `json:"-"` 26 | FailureCount int `json:"failureCount"` 27 | SuccessCount int `json:"successCount"` 28 | } 29 | 30 | // UnmarshalJSON unmarshals a ClusterSummary. 31 | func (c *ClusterSummary) UnmarshalJSON(data []byte) error { 32 | type Alias ClusterSummary 33 | aux := &struct { 34 | *Alias 35 | }{ 36 | Alias: (*Alias)(c), 37 | } 38 | 39 | if err := json.Unmarshal(data, &aux); err != nil { 40 | return err 41 | } 42 | 43 | c.LatestReportSet.Cluster = c.Cluster 44 | 45 | for idx := range c.LatestReportSet.Reports { 46 | c.LatestReportSet.Reports[idx].Cluster = c.LatestReportSet.Cluster 47 | c.LatestReportSet.Reports[idx].Timestamp = c.LatestReportSet.Timestamp 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /api/cluster_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestClusterSummaryUnmarshalJSON(t *testing.T) { 11 | data := `{ 12 | "cluster": "exampleCluster", 13 | "latestReportSet": { 14 | "timestamp": "2015-10-21T07:28:42Z", 15 | "failureCount": 4, 16 | "successCount": 1, 17 | "reports": [ 18 | { 19 | "id": "exampleReport1", 20 | "package": "examplePackage.ID.1", 21 | "failureCount": 2, 22 | "successCount": 1 23 | }, 24 | { 25 | "id": "exampleReport2", 26 | "package": "examplePackage.ID.2", 27 | "failureCount": 2, 28 | "successCount": 0 29 | } 30 | ] 31 | } 32 | }` 33 | ts, err := time.Parse(TimeFormat, "2015-10-21T07:28:42Z") 34 | if err != nil { 35 | t.Fatalf("%+v", err) 36 | } 37 | 38 | want := ClusterSummary{ 39 | Cluster: "exampleCluster", 40 | LatestReportSet: &ReportSet{ 41 | Cluster: "exampleCluster", 42 | Timestamp: Time{Time: ts}, 43 | FailureCount: 4, 44 | SuccessCount: 1, 45 | Reports: []*ReportSummary{ 46 | { 47 | ID: "exampleReport1", 48 | Package: "examplePackage.ID.1", 49 | Cluster: "exampleCluster", 50 | Timestamp: Time{Time: ts}, 51 | FailureCount: 2, 52 | SuccessCount: 1, 53 | }, 54 | { 55 | ID: "exampleReport2", 56 | Package: "examplePackage.ID.2", 57 | Cluster: "exampleCluster", 58 | Timestamp: Time{Time: ts}, 59 | FailureCount: 2, 60 | SuccessCount: 0, 61 | }, 62 | }, 63 | }, 64 | } 65 | 66 | var got ClusterSummary 67 | err = json.Unmarshal([]byte(data), &got) 68 | if err != nil { 69 | t.Fatalf("unexpected error: %+v", err) 70 | } 71 | 72 | if !reflect.DeepEqual(got, want) { 73 | t.Fatalf("got != want: got=%+v, want=%+v", got, want) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /api/common.go: -------------------------------------------------------------------------------- 1 | // Package api provides types for Preflight reports and some common helpers. 2 | package api 3 | 4 | import ( 5 | "encoding/json" 6 | "time" 7 | ) 8 | 9 | // TimeFormat defines the format used for timestamps across all this API. 10 | const TimeFormat = time.RFC3339 11 | 12 | // Time is a wrapper around time.Time that overrides how it is marshaled into JSON 13 | type Time struct { 14 | time.Time 15 | } 16 | 17 | // String returns a string representation of the timestamp 18 | func (t Time) String() string { 19 | return t.Format(TimeFormat) 20 | } 21 | 22 | // MarshalJSON marshals the timestamp with RFC3339 format 23 | func (t Time) MarshalJSON() ([]byte, error) { 24 | str := t.String() 25 | jsonStr, err := json.Marshal(str) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return []byte(jsonStr), nil 30 | } 31 | -------------------------------------------------------------------------------- /api/datareading.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | ) 7 | 8 | // DataReadingsPost is the payload in the upload request. 9 | type DataReadingsPost struct { 10 | AgentMetadata *AgentMetadata `json:"agent_metadata"` 11 | // DataGatherTime represents the time that the data readings were gathered 12 | DataGatherTime time.Time `json:"data_gather_time"` 13 | DataReadings []*DataReading `json:"data_readings"` 14 | } 15 | 16 | // DataReading is the output of a DataGatherer. 17 | type DataReading struct { 18 | // ClusterID is optional as it can be infered from the agent 19 | // token when using basic authentication. 20 | ClusterID string `json:"cluster_id,omitempty"` 21 | DataGatherer string `json:"data-gatherer"` 22 | Timestamp Time `json:"timestamp"` 23 | Data interface{} `json:"data"` 24 | SchemaVersion string `json:"schema_version"` 25 | } 26 | 27 | // GatheredResource wraps the raw k8s resource that is sent to the jetstack secure backend 28 | type GatheredResource struct { 29 | // Resource is a reference to a k8s object that was found by the informer 30 | // should be of type unstructured.Unstructured, raw Object 31 | Resource interface{} 32 | DeletedAt Time 33 | } 34 | 35 | func (v GatheredResource) MarshalJSON() ([]byte, error) { 36 | dateString := "" 37 | if !v.DeletedAt.IsZero() { 38 | dateString = v.DeletedAt.Format(TimeFormat) 39 | } 40 | 41 | data := struct { 42 | Resource interface{} `json:"resource"` 43 | DeletedAt string `json:"deleted_at,omitempty"` 44 | }{ 45 | Resource: v.Resource, 46 | DeletedAt: dateString, 47 | } 48 | 49 | return json.Marshal(data) 50 | } 51 | -------------------------------------------------------------------------------- /api/datareading_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestJSONGatheredResourceDropsEmptyTime(t *testing.T) { 10 | var resource GatheredResource 11 | bytes, err := json.Marshal(resource) 12 | if err != nil { 13 | t.Fatalf("failed to marshal %s", err) 14 | } 15 | 16 | expected := `{"resource":null}` 17 | 18 | if string(bytes) != expected { 19 | t.Fatalf("unexpected json \ngot %s\nwant %s", string(bytes), expected) 20 | } 21 | } 22 | 23 | func TestJSONGatheredResourceSetsTimeWhenPresent(t *testing.T) { 24 | var resource GatheredResource 25 | resource.DeletedAt = Time{time.Date(2021, 3, 29, 0, 0, 0, 0, time.UTC)} 26 | bytes, err := json.Marshal(resource) 27 | if err != nil { 28 | t.Fatalf("failed to marshal %s", err) 29 | } 30 | 31 | expected := `{"resource":null,"deleted_at":"2021-03-29T00:00:00Z"}` 32 | 33 | if string(bytes) != expected { 34 | t.Fatalf("unexpected json \ngot %s\nwant %s", string(bytes), expected) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/report.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // Report contains the fields of a Preflight report 4 | type Report struct { 5 | // Unique ID of the report. 6 | ID string `json:"id"` 7 | // PreflightVersion indicates the version of preflight this report was generated with. 8 | PreflightVersion string `json:"preflight-version"` 9 | // Timestamp indicates when the report was generated. 10 | Timestamp Time `json:"timestamp"` 11 | // Cluster indicates which was the target of the report. 12 | Cluster string `json:"cluster"` 13 | // Package indicates which package was used for the report. (deprecated) 14 | Package string `json:"package"` 15 | // PackageInformation contains all the information about the package that was used to generate the report. 16 | PackageInformation PackageInformation `json:"package-information"` 17 | // Name is the name of the package that was used for this report. 18 | Name string `json:"name"` 19 | // Description is the description of the package that was used for this report. 20 | Description string `json:"description,omitempty"` 21 | // Sections contains the sections of the package that was used for this report. 22 | Sections []ReportSection `json:"sections,omitempty"` 23 | } 24 | 25 | // Summarize produces as ReportSummary from a Report 26 | func (r *Report) Summarize() ReportSummary { 27 | var successes, failures int 28 | for _, section := range r.Sections { 29 | for _, rule := range section.Rules { 30 | if rule.Success { 31 | successes++ 32 | } else { 33 | failures++ 34 | } 35 | } 36 | } 37 | 38 | return ReportSummary{ 39 | ID: r.ID, 40 | Package: r.Package, 41 | Cluster: r.Cluster, 42 | Timestamp: r.Timestamp, 43 | FailureCount: failures, 44 | SuccessCount: successes, 45 | } 46 | } 47 | 48 | // GetReportMetadata returns the ReportMetadata for the Report. 49 | func (r *Report) GetReportMetadata() *ReportMetadata { 50 | return &ReportMetadata{ 51 | ID: r.ID, 52 | Timestamp: r.Timestamp, 53 | Cluster: r.Cluster, 54 | Package: r.Package, 55 | PackageInformation: r.PackageInformation, 56 | PreflightVersion: r.PreflightVersion, 57 | } 58 | } 59 | 60 | // PackageInformation contains all the details to identify a package. 61 | type PackageInformation struct { 62 | // Namespace the package belongs to. 63 | Namespace string `json:"namespace"` 64 | // ID is the ID of the package. 65 | ID string `json:"id"` 66 | // Version is the version of the package. 67 | Version string `json:"version"` 68 | // SchemaVersion is the version of the Preflight package schema. 69 | SchemaVersion string `json:"schema-version"` 70 | } 71 | 72 | // ReportSection contains the fields of a section inside a Report 73 | type ReportSection struct { 74 | // ID is the ID of the section. 75 | ID string `json:"id"` 76 | // Name is the name of the section. 77 | Name string `json:"name"` 78 | // Description is the description of the section. 79 | Description string `json:"description,omitempty"` 80 | // Rules contain all the rules in the section. 81 | Rules []ReportRule `json:"rules,omitempty"` 82 | } 83 | 84 | // ReportRule contains the fields of a rule inside a Report 85 | type ReportRule struct { 86 | // ID is the id of the rule. 87 | ID string `json:"id"` 88 | // Name is a shortname for the rule. 89 | Name string `json:"name"` 90 | // Description is a text describing what the rule is about. 91 | Description string `json:"description,omitempty"` 92 | // Manual indicated whether the rule can be evaluated automatically by Preflight or requires manual intervention. 93 | Manual bool `json:"manual,omitempty"` 94 | // Remediation is a text describing how to fix a failure of the rule. 95 | Remediation string `json:"remediation,omitempty"` 96 | // Links contains useful links related to the rule. 97 | Links []string `json:"links,omitempty"` 98 | // Success indicates whether the check was a success or not. 99 | Success bool `json:"success"` 100 | // Value contains the raw result of the check. 101 | Value interface{} `json:"value,omitempty"` 102 | // Violations contains the list of messages from the execution of the check 103 | Violations interface{} `json:"violations"` 104 | // Missing indicates whether the Rego rule was missing or not. 105 | Missing bool `json:"missing"` 106 | } 107 | 108 | // ReportMetadata contains metadata about a report 109 | type ReportMetadata struct { 110 | // Unique ID of the report. 111 | ID string `json:"id"` 112 | // Timestamp indicates when the report was generated. 113 | Timestamp Time `json:"timestamp"` 114 | // Cluster indicates which was the target of the report. 115 | Cluster string `json:"cluster"` 116 | // Deprecated: Package indicates which package was used for the report. 117 | Package string `json:"package"` 118 | // PackageInformation contains all the information about the package that was used to generate the report. 119 | PackageInformation PackageInformation `json:"package-information"` 120 | // PreflightVersion indicates the version of preflight this report was generated with. 121 | PreflightVersion string `json:"preflight-version"` 122 | } 123 | -------------------------------------------------------------------------------- /cmd/agent.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/jetstack/preflight/pkg/agent" 10 | "github.com/jetstack/preflight/pkg/permissions" 11 | ) 12 | 13 | var agentCmd = &cobra.Command{ 14 | Use: "agent", 15 | Short: "start the preflight agent", 16 | Long: `The agent will periodically gather data for the configured data 17 | gatherers and send it to a remote backend for evaluation`, 18 | RunE: agent.Run, 19 | } 20 | 21 | var agentInfoCmd = &cobra.Command{ 22 | Use: "info", 23 | Short: "print several internal parameters of the agent", 24 | Long: `Print several internal parameters of the agent, as the built-in OAuth2 client ID.`, 25 | Run: func(cmd *cobra.Command, args []string) { 26 | printVersion(true) 27 | fmt.Println() 28 | printOAuth2Config() 29 | }, 30 | } 31 | 32 | var agentRBACCmd = &cobra.Command{ 33 | Use: "rbac", 34 | Short: "print the agent's minimal RBAC manifest", 35 | Long: `Print RBAC string by reading GVRs`, 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | 38 | b, err := os.ReadFile(agent.Flags.ConfigFilePath) 39 | if err != nil { 40 | return fmt.Errorf("Failed to read config file: %s", err) 41 | } 42 | cfg, err := agent.ParseConfig(b) 43 | if err != nil { 44 | return fmt.Errorf("Failed to parse config file: %s", err) 45 | } 46 | 47 | err = agent.ValidateDataGatherers(cfg.DataGatherers) 48 | if err != nil { 49 | return fmt.Errorf("Failed to validate data gatherers: %s", err) 50 | } 51 | 52 | out := permissions.GenerateFullManifest(cfg.DataGatherers) 53 | fmt.Print(out) 54 | return nil 55 | }, 56 | } 57 | 58 | func init() { 59 | rootCmd.AddCommand(agentCmd) 60 | agentCmd.AddCommand(agentInfoCmd) 61 | agentCmd.AddCommand(agentRBACCmd) 62 | agent.InitAgentCmdFlags(agentCmd, &agent.Flags) 63 | } 64 | -------------------------------------------------------------------------------- /cmd/agent_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os" 7 | "os/exec" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | // TestAgentRunOneShot runs the agent in `--one-shot` mode and verifies that it exits 15 | // after the first data gathering iteration. 16 | func TestAgentRunOneShot(t *testing.T) { 17 | if _, found := os.LookupEnv("GO_CHILD"); found { 18 | // Silence the warning about missing pod name for event generation 19 | // TODO(wallrj): This should not be required when an `--input-file` has been supplied. 20 | t.Setenv("POD_NAME", "venafi-kubernetes-e2e") 21 | // Silence the error about missing kubeconfig. 22 | // TODO(wallrj): This should not be required when an `--input-file` has been supplied. 23 | t.Setenv("KUBECONFIG", "testdata/agent/one-shot/success/kubeconfig.yaml") 24 | 25 | os.Args = []string{ 26 | "preflight", 27 | "agent", 28 | "--one-shot", 29 | // TODO(wallrj): This should not be required when an `--input-file` has been supplied. 30 | "--api-token=should-not-be-required", 31 | // TODO(wallrj): This should not be required when an `--input-file` has been supplied. 32 | "--install-namespace=default", 33 | "--agent-config-file=testdata/agent/one-shot/success/config.yaml", 34 | "--input-path=testdata/agent/one-shot/success/input.json", 35 | "--output-path=/dev/null", 36 | "-v=1", 37 | } 38 | Execute() 39 | return 40 | } 41 | t.Log("Running child process") 42 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) 43 | defer cancel() 44 | cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestAgentRunOneShot$") 45 | var ( 46 | stdout bytes.Buffer 47 | stderr bytes.Buffer 48 | ) 49 | cmd.Stdout = &stdout 50 | cmd.Stderr = &stderr 51 | cmd.Env = append( 52 | os.Environ(), 53 | "GO_CHILD=true", 54 | ) 55 | err := cmd.Run() 56 | 57 | stdoutStr := stdout.String() 58 | stderrStr := stderr.String() 59 | t.Logf("STDOUT\n%s\n", stdoutStr) 60 | t.Logf("STDERR\n%s\n", stderrStr) 61 | require.NoError(t, err, context.Cause(ctx)) 62 | } 63 | -------------------------------------------------------------------------------- /cmd/echo.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/jetstack/preflight/pkg/echo" 7 | ) 8 | 9 | var echoCmd = &cobra.Command{ 10 | Use: "echo", 11 | Short: "starts an echo server to test the agent", 12 | Long: `The agent sends data to a server. This echo server 13 | can be used to act as the server part and echo the data received by the agent.`, 14 | RunE: echo.Echo, 15 | } 16 | 17 | func init() { 18 | rootCmd.AddCommand(echoCmd) 19 | echoCmd.PersistentFlags().StringVarP( 20 | &echo.EchoListen, 21 | "listen", 22 | "l", 23 | ":8080", 24 | "Address where to listen.", 25 | ) 26 | 27 | echoCmd.PersistentFlags().BoolVarP( 28 | &echo.Compact, 29 | "compact", 30 | "", 31 | false, 32 | "Prints compact output.", 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/helpers.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | "github.com/jetstack/preflight/pkg/client" 8 | "github.com/jetstack/preflight/pkg/version" 9 | ) 10 | 11 | func printVersion(verbose bool) { 12 | fmt.Println("Preflight version: ", version.PreflightVersion, runtime.GOOS+"/"+runtime.GOARCH) 13 | if verbose { 14 | fmt.Println(" Commit: ", version.Commit) 15 | fmt.Println(" Built: ", version.BuildDate) 16 | fmt.Println(" Go: ", runtime.Version()) 17 | } 18 | } 19 | 20 | func printOAuth2Config() { 21 | fmt.Println("OAuth2: ") 22 | fmt.Println(" ClientID: ", client.ClientID) 23 | fmt.Println(" AuthServerDomain: ", client.AuthServerDomain) 24 | } 25 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/spf13/pflag" 11 | "k8s.io/klog/v2" 12 | 13 | "github.com/jetstack/preflight/pkg/logs" 14 | ) 15 | 16 | // rootCmd represents the base command when called without any subcommands 17 | var rootCmd = &cobra.Command{ 18 | Use: "preflight", 19 | Short: "Kubernetes cluster configuration checker 🚀", 20 | Long: `Preflight is a tool to automatically perform Kubernetes cluster 21 | configuration checks using Open Policy Agent (OPA). 22 | 23 | Preflight checks are bundled into Packages`, 24 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 25 | return logs.Initialize() 26 | }, 27 | // SilenceErrors and SilenceUsage prevents this command or any sub-command 28 | // from printing arbitrary text to stderr. 29 | // Why? To ensure that each line of output can be parsed as a single message 30 | // for consumption by logging agents such as fluentd. 31 | // Usage information is still available on stdout with the `-h` and `--help` 32 | // flags. 33 | SilenceErrors: true, 34 | SilenceUsage: true, 35 | } 36 | 37 | func init() { 38 | for _, command := range rootCmd.Commands() { 39 | setFlagsFromEnv("PREFLIGHT_", command.PersistentFlags()) 40 | } 41 | } 42 | 43 | // Execute adds all child commands to the root command and sets flags appropriately. 44 | // This is called by main.main(). It only needs to happen once to the rootCmd. 45 | // If the root command or sub-command returns an error, the error message will 46 | // will be logged and the process will exit with status 1. 47 | func Execute() { 48 | logs.AddFlags(rootCmd.PersistentFlags()) 49 | ctx := klog.NewContext(context.Background(), klog.Background()) 50 | var exitCode int 51 | if err := rootCmd.ExecuteContext(ctx); err != nil { 52 | exitCode = 1 53 | klog.ErrorS(err, "Exiting due to error", "exit-code", exitCode) 54 | } 55 | klog.FlushAndExit(klog.ExitFlushTimeout, exitCode) 56 | } 57 | 58 | func setFlagsFromEnv(prefix string, fs *pflag.FlagSet) { 59 | set := map[string]bool{} 60 | fs.Visit(func(f *pflag.Flag) { 61 | set[f.Name] = true 62 | }) 63 | fs.VisitAll(func(f *pflag.Flag) { 64 | // ignore flags set from the commandline 65 | if set[f.Name] { 66 | return 67 | } 68 | // remove trailing _ to reduce common errors with the prefix, i.e. people setting it to MY_PROG_ 69 | cleanPrefix := strings.TrimSuffix(prefix, "_") 70 | name := fmt.Sprintf("%s_%s", cleanPrefix, strings.Replace(strings.ToUpper(f.Name), "-", "_", -1)) 71 | if e, ok := os.LookupEnv(name); ok { 72 | _ = f.Value.Set(e) 73 | } 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /cmd/testdata/agent/one-shot/success/config.yaml: -------------------------------------------------------------------------------- 1 | # Just enough venafi-kubernetes-agent config to allow it to run with an input 2 | # file in one-shot mode. 3 | cluster_id: "venafi-kubernetes-agent-e2e" 4 | organization_id: "venafi-kubernetes-agent-e2e" 5 | -------------------------------------------------------------------------------- /cmd/testdata/agent/one-shot/success/input.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /cmd/testdata/agent/one-shot/success/kubeconfig.yaml: -------------------------------------------------------------------------------- 1 | # Just enough kubeconfig to satisfy client-go 2 | apiVersion: v1 3 | kind: Config 4 | current-context: cluster-1 5 | contexts: 6 | - name: cluster-1 7 | context: 8 | cluster: cluster-1 9 | user: user-1 10 | clusters: 11 | - name: cluster-1 12 | cluster: 13 | server: https://192.0.2.1:8443 14 | preferences: {} 15 | users: [] 16 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var verbose bool 8 | 9 | var versionCmd = &cobra.Command{ 10 | Use: "version", 11 | Short: "Display the version", 12 | Long: `Display preflight version. 13 | `, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | printVersion(verbose) 16 | }, 17 | } 18 | 19 | func init() { 20 | rootCmd.AddCommand(versionCmd) 21 | versionCmd.PersistentFlags().BoolVarP( 22 | &verbose, 23 | "verbose", 24 | "v", 25 | false, 26 | "If enabled, displays the additional information about this built.", 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/.helmignore: -------------------------------------------------------------------------------- 1 | tests 2 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: jetstack-agent 3 | description: TLS Protect for Kubernetes Agent 4 | type: application 5 | version: 0.4.0 6 | appVersion: "v0.1.43" 7 | home: https://github.com/jetstack/jetstack-secure 8 | maintainers: 9 | - name: JSCP and CRE Team 10 | email: tls-protect-for-kubernetes@jetstack.io 11 | url: https://platform.jetstack.io/documentation 12 | sources: 13 | - https://github.com/jetstack/jetstack-secure 14 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Please make sure you have the credentials secret: "{{ .Values.authentication.secretName }}" available 2 | > kubectl get secret -n {{ .Release.Namespace }} {{ .Values.authentication.secretName }} 3 | 4 | 2. Check the application is running with the following: 5 | > kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} 6 | 7 | 3. Check the application logs for successful connection to the platform: 8 | > kubectl logs -n {{ .Release.Namespace }} $(kubectl get pod -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} -o jsonpath='{.items[0].metadata.name}') 9 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "jetstack-agent.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 "jetstack-agent.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" $name .Release.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 "jetstack-agent.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "jetstack-agent.labels" -}} 37 | helm.sh/chart: {{ include "jetstack-agent.chart" . }} 38 | {{ include "jetstack-agent.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 "jetstack-agent.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "jetstack-agent.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 "jetstack-agent.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "jetstack-agent.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: {{ include "jetstack-agent.fullname" . }} 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "jetstack-agent.labels" . | nindent 4 }} 9 | spec: 10 | replicas: {{ .Values.replicaCount }} 11 | selector: 12 | matchLabels: 13 | {{- include "jetstack-agent.selectorLabels" . | nindent 6 }} 14 | template: 15 | metadata: 16 | {{- with .Values.podAnnotations }} 17 | annotations: 18 | {{- toYaml . | nindent 8 }} 19 | {{- end }} 20 | labels: 21 | {{- include "jetstack-agent.selectorLabels" . | nindent 8 }} 22 | spec: 23 | {{- with .Values.imagePullSecrets }} 24 | imagePullSecrets: 25 | {{- toYaml . | nindent 8 }} 26 | {{- end }} 27 | serviceAccountName: {{ include "jetstack-agent.serviceAccountName" . }} 28 | securityContext: 29 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 30 | containers: 31 | - name: {{ .Chart.Name }} 32 | securityContext: 33 | {{- toYaml .Values.securityContext | nindent 12 }} 34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | {{- if eq .Values.authentication.type "token" }} 37 | env: 38 | - name: API_TOKEN 39 | valueFrom: 40 | secretKeyRef: 41 | name: {{ default "agent-credentials" .Values.authentication.secretName }} 42 | key: {{ default "apitoken" .Values.authentication.secretKey }} 43 | {{- end }} 44 | {{- if not (empty .Values.command) }} 45 | command: 46 | {{- range .Values.command }} 47 | - {{ . | quote }} 48 | {{- end }} 49 | {{- end }} 50 | args: 51 | - "agent" 52 | - "-c" 53 | {{- if .Values.config.override.enabled }} 54 | - "/etc/jetstack-secure/agent/config/{{ default "config.yaml" .Values.config.override.configmap.key }}" 55 | {{- else }} 56 | - "/etc/jetstack-secure/agent/config/config.yaml" 57 | {{- end }} 58 | {{- if eq .Values.authentication.type "file" }} 59 | - "-k" 60 | - "/etc/jetstack-secure/agent/credentials/{{ default "credentials.json" .Values.authentication.secretKey}}" 61 | {{- end }} 62 | - "-p" 63 | - "0h1m0s" 64 | {{- range .Values.extraArgs }} 65 | - {{ . | quote }} 66 | {{- end }} 67 | resources: 68 | {{- toYaml .Values.resources | nindent 12 }} 69 | volumeMounts: 70 | - name: config 71 | mountPath: "/etc/jetstack-secure/agent/config" 72 | readOnly: true 73 | {{- if eq .Values.authentication.type "file" }} 74 | - name: credentials 75 | mountPath: "/etc/jetstack-secure/agent/credentials" 76 | readOnly: true 77 | {{- end }} 78 | {{- with .Values.volumeMounts }} 79 | {{- toYaml . | nindent 12 }} 80 | {{- end }} 81 | {{- with .Values.nodeSelector }} 82 | nodeSelector: 83 | {{- toYaml . | nindent 8 }} 84 | {{- end }} 85 | {{- with .Values.affinity }} 86 | affinity: 87 | {{- toYaml . | nindent 8 }} 88 | {{- end }} 89 | {{- with .Values.tolerations }} 90 | tolerations: 91 | {{- toYaml . | nindent 8 }} 92 | {{- end }} 93 | volumes: 94 | {{- if .Values.config.override.enabled }} 95 | - name: config 96 | configMap: 97 | name: {{ default "agent-config" .Values.config.override.configmap.name }} 98 | optional: false 99 | {{- else }} 100 | - name: config 101 | configMap: 102 | name: agent-config 103 | optional: false 104 | {{- end }} 105 | - name: credentials 106 | secret: 107 | secretName: {{ default "agent-credentials" .Values.authentication.secretName }} 108 | optional: false 109 | {{- with .Values.volumes }} 110 | {{- toYaml . | nindent 8 }} 111 | {{- end }} 112 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.authentication.createSecret -}} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ .Values.authentication.secretName}} 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "jetstack-agent.labels" . | nindent 4 }} 9 | type: Opaque 10 | data: 11 | {{ default "credentials.json" .Values.authentication.secretKey}}: {{ .Values.authentication.secretValue }} 12 | {{- end -}} 13 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "jetstack-agent.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "jetstack-agent.labels" . | nindent 4 }} 9 | {{- with .Values.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/tests/configuration_test.yaml: -------------------------------------------------------------------------------- 1 | suite: test configuration overrides 2 | templates: 3 | - templates/configmap.yaml 4 | 5 | tests: 6 | - it: should not generate a configmap when name and override given 7 | set: 8 | config.override.enabled: true 9 | config.override.configmap.name: custom-agent-config 10 | template: configmap.yaml 11 | asserts: 12 | # No congifmap is produced 13 | - hasDocuments: 14 | count: 0 15 | 16 | - it: embedded config is only config in configmap 17 | set: 18 | config.override.enabled: true 19 | values: 20 | - values/custom-config.yaml 21 | template: configmap.yaml 22 | asserts: 23 | # ConfigMap is generated 24 | - containsDocument: 25 | kind: ConfigMap 26 | apiVersion: v1 27 | name: agent-config 28 | documentIndex: 0 29 | # Assert ths content matches the input 30 | # This was tricky due to the |- and the alphabetical ordering 31 | - equal: 32 | path: data 33 | value: 34 | config.yaml: |- 35 | cluster_id: test_cluster 36 | data-gatherers: 37 | - kind: k8s-discovery 38 | name: k8s-discovery 39 | organization_id: test_org 40 | server: https://platform.jetstack.io 41 | 42 | # This checks the configmap is rendered properly when required config is given 43 | - it: render correctly when only required config is given 44 | set: 45 | config.organisation: test_org 46 | config.cluster: test_cluster 47 | template: configmap.yaml 48 | asserts: 49 | - hasDocuments: 50 | count: 1 51 | - isKind: 52 | of: ConfigMap 53 | - isAPIVersion: 54 | of: v1 55 | - matchSnapshot: {} 56 | 57 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/tests/deployment_test.yaml: -------------------------------------------------------------------------------- 1 | suite: test deployment 2 | templates: 3 | - deployment.yaml 4 | 5 | tests: 6 | # Basic checks on deployment 7 | - it: templates as expected 8 | set: 9 | image.tag: latest 10 | config.organisation: test_org 11 | config.cluster: test_cluster 12 | template: deployment.yaml 13 | asserts: 14 | - isKind: 15 | of: Deployment 16 | # Validate name matches 17 | - matchRegex: 18 | path: metadata.name 19 | pattern: ^jetstack-agent-* 20 | # Check is latest is set as tag that it uses that tag 21 | - equal: 22 | path: spec.template.spec.containers[0].image 23 | value: quay.io/jetstack/preflight:latest 24 | 25 | # Check naming works with nameOverride 26 | - it: Deployment name is set when nameOverride is used 27 | set: 28 | config.organisation: test_org 29 | config.cluster: test_cluster 30 | nameOverride: example 31 | template: deployment.yaml 32 | asserts: 33 | - isKind: 34 | of: Deployment 35 | - matchRegex: 36 | path: metadata.name 37 | pattern: ^example-RELEASE-NAME$ 38 | # see example output as why this has to be like this. It's more subtle than 39 | # the fullnameOverride: 40 | # Actual: 41 | # agent-RELEASE-NAME 42 | # Diff: 43 | # --- Expected 44 | # +++ Actual 45 | # @@ -1,2 +1,2 @@ 46 | # -agent-* 47 | # +agent-RELEASE-NAME 48 | 49 | # Check similar with fullnameOverride 50 | - it: Deployment name is set when fullnameOverride is used 51 | set: 52 | config.organisation: test_org 53 | config.cluster: test_cluster 54 | fullnameOverride: example 55 | template: deployment.yaml 56 | asserts: 57 | - isKind: 58 | of: Deployment 59 | - equal: 60 | path: metadata.name 61 | value: example 62 | 63 | # Checking extraArgs are passed 64 | - it: Extra Args passed in a valid format when supplied 65 | set: 66 | config.organisation: test_org 67 | config.cluster: test_cluster 68 | extraArgs: ["--strict", "--one-shot"] 69 | template: deployment.yaml 70 | asserts: 71 | - isKind: 72 | of: Deployment 73 | - contains: 74 | path: spec.template.spec.containers[0].args 75 | content: --strict 76 | - contains: 77 | path: spec.template.spec.containers[0].args 78 | content: --one-shot 79 | 80 | # Check command is present when configured 81 | - it: Command passes to deployment manifest 82 | set: 83 | config.organisation: test_org 84 | config.cluster: test_cluster 85 | command: ["notpreflight"] 86 | template: deployment.yaml 87 | asserts: 88 | - isKind: 89 | of: Deployment 90 | - contains: 91 | path: spec.template.spec.containers[0].command 92 | content: notpreflight 93 | 94 | # Check the volumes and volumeMounts works correctly 95 | - it: Volumes and VolumeMounts added correctly 96 | set: 97 | config.organisation: test_org 98 | config.cluster: test_cluster 99 | values: 100 | - ./values/custom-volumes.yaml 101 | asserts: 102 | - isKind: 103 | of: Deployment 104 | - equal: 105 | # In template this comes after credentials and agent config volumeMounts 106 | path: spec.template.spec.containers[0].volumeMounts[?(@.name == "cabundle")] 107 | value: 108 | mountPath: /etc/ssl/certs/ 109 | name: cabundle 110 | readOnly: true 111 | - equal: 112 | path: spec.template.spec.volumes[?(@.name == "cabundle")].configmap 113 | value: 114 | defaultMode: 420 115 | name: cabundle 116 | optional: true 117 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/tests/values/custom-config.yaml: -------------------------------------------------------------------------------- 1 | # -- Configuration section for the Jetstack Agent itself 2 | config: 3 | # -- Provide an Override to allow completely custom agent configuration 4 | override: 5 | # -- Override disabled by default 6 | enabled: true 7 | # -- Embed the agent configuration here in the chart values 8 | config: 9 | server: "https://platform.jetstack.io" 10 | organization_id: test_org 11 | cluster_id: test_cluster 12 | data-gatherers: 13 | # gather k8s apiserver version information 14 | - kind: "k8s-discovery" 15 | name: "k8s-discovery" 16 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/tests/values/custom-volumes.yaml: -------------------------------------------------------------------------------- 1 | volumes: 2 | - name: cabundle 3 | configmap: 4 | name: cabundle 5 | optional: true 6 | defaultMode: 0644 7 | 8 | volumeMounts: 9 | - name: cabundle 10 | readOnly: true 11 | mountPath: /etc/ssl/certs/ 12 | -------------------------------------------------------------------------------- /deploy/charts/jetstack-agent/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for jetstack-agent. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | # -- default replicas, do not scale up 6 | replicaCount: 1 7 | 8 | image: 9 | # -- Default to Open Source image repository 10 | repository: quay.io/jetstack/preflight 11 | # -- Defaults to only pull if not already present 12 | pullPolicy: IfNotPresent 13 | # -- Overrides the image tag whose default is the chart appVersion 14 | tag: "v0.1.43" 15 | 16 | # -- Specify image pull credentials if using a prviate registry 17 | imagePullSecrets: [] 18 | 19 | # -- Helm default setting to override release name, leave blank 20 | nameOverride: "" 21 | # -- Helm default setting, use this to shorten install name 22 | fullnameOverride: "" 23 | 24 | serviceAccount: 25 | # -- Specifies whether a service account should be created 26 | # @default true 27 | create: true 28 | # -- Annotations to add to the service account 29 | annotations: {} 30 | # The name of the service account to use. 31 | # If not set and create is true, a name is generated using the fullname template 32 | name: "" 33 | 34 | podAnnotations: {} 35 | 36 | podSecurityContext: {} 37 | # fsGroup: 2000 38 | 39 | securityContext: 40 | capabilities: 41 | drop: 42 | - ALL 43 | readOnlyRootFilesystem: true 44 | runAsNonRoot: true 45 | runAsUser: 1000 46 | 47 | resources: 48 | # We usually recommend not to specify default resources and to leave this as a conscious 49 | # choice for the user. This also increases chances charts run on environments with little 50 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 51 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 52 | requests: 53 | memory: 200Mi 54 | cpu: 200m 55 | limits: 56 | memory: 500Mi 57 | cpu: 500m 58 | 59 | nodeSelector: {} 60 | 61 | tolerations: [] 62 | 63 | affinity: {} 64 | 65 | # -- Additional volumes to add to the jetstack-agent pod. 66 | volumes: [] 67 | 68 | # -- Additional volume mounts to add to the jetstack-agent container. 69 | volumeMounts: [] 70 | 71 | # -- Override the jetstack-agent entrypoint with specified command. 72 | command: [] 73 | 74 | # -- Add additional arguments to the default `agent` command. 75 | extraArgs: [] 76 | 77 | # -- Authentication section for the agent 78 | authentication: 79 | # -- Reccomend that you do not use this and instead creat the credential secret outside of helm 80 | createSecret: false 81 | # -- Type can be "file"/"token" determining how the agent should authenticate the to the backend 82 | type: file 83 | # -- Name of the secret containing agent credentials.json 84 | secretName: agent-credentials 85 | # -- Key name in secret 86 | secretKey: "credentials.json" 87 | # -- Base64 encoded value from Jetstack Secure Dashboard - only required when createSecret is true 88 | secretValue: "" 89 | 90 | # -- Configuration section for the Jetstack Agent itself 91 | config: 92 | # -- Overrides the server if using a proxy between agent and Jetstack Secure 93 | server: "https://platform.jetstack.io" 94 | # -- REQUIRED - Your Jetstack Secure Organisation Name 95 | organisation: "" 96 | # -- REQUIRED - Your Jetstack Secure Cluster Name 97 | cluster: "" 98 | # -- Send data back to the platform every minute unless changed 99 | period: "0h1m0s" 100 | 101 | # -- Configure data that is gathered from your cluster, for full details see https://platform.jetstack.io/documentation/configuration/jetstack-agent/configuration 102 | dataGatherers: 103 | # -- Use the standard full set of data gatherers 104 | default: true 105 | # -- A list of data gatherers to limit agent scope 106 | custom: [] 107 | # Full list that makes up the default role 108 | # - node 109 | # - secret 110 | # - cert-manager 111 | # - googlecas 112 | # - awspca 113 | # - webhook 114 | # - openshift 115 | # - istio 116 | # - venafienhancedissuer 117 | 118 | # -- Provide an Override to allow completely custom agent configuration 119 | override: 120 | # -- Override disabled by default 121 | enabled: false 122 | # -- Embed the agent configuration here in the chart values 123 | config: 124 | # -- Sepcify ConfigMap details to load config from existing ConfigMap 125 | configmap: 126 | name: 127 | key: 128 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/.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 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: venafi-kubernetes-agent 3 | type: application 4 | 5 | description: |- 6 | The Venafi Kubernetes Agent connects your Kubernetes or Openshift cluster to the Venafi Control Plane. 7 | 8 | maintainers: 9 | - name: Venafi 10 | email: support@venafi.cloud 11 | url: https://venafi.com 12 | 13 | sources: 14 | - https://github.com/jetstack/jetstack-secure 15 | 16 | # These versions are meant to be overridden by `make helm-chart`. No `v` prefix 17 | # for the `version` because Helm doesn't support auto-determining the latest 18 | # version for OCI Helm charts that use a `v` prefix. 19 | version: 0.0.0 20 | appVersion: "v0.0.0" 21 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/crd_bases/crd.footer.yaml: -------------------------------------------------------------------------------- 1 | {{ end }} 2 | {{ end }} 3 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/crd_bases/crd.header-without-validations.yaml: -------------------------------------------------------------------------------- 1 | {{/* DO NOT EDIT. Use 'make generate-crds-venconn' to regenerate. */}} 2 | {{- if .Values.crds.venafiConnection.include }} 3 | {{- if (or (semverCompare "<1.25" .Capabilities.KubeVersion.GitVersion) .Values.crds.forceRemoveValidationAnnotations) }} 4 | apiVersion: apiextensions.k8s.io/v1 5 | kind: CustomResourceDefinition 6 | metadata: 7 | name: "venaficonnections.jetstack.io" 8 | {{- if .Values.crds.keep }} 9 | annotations: 10 | # This annotation prevents the CRD from being pruned by Helm when this chart 11 | # is deleted. 12 | helm.sh/resource-policy: keep 13 | {{- end }} 14 | labels: 15 | {{- include "venafi-connection.labels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/crd_bases/crd.header.yaml: -------------------------------------------------------------------------------- 1 | {{/* DO NOT EDIT. Use 'make generate-crds-venconn' to regenerate. */}} 2 | {{- if .Values.crds.venafiConnection.include }} 3 | {{- if not (or (semverCompare "<1.25" .Capabilities.KubeVersion.GitVersion) .Values.crds.forceRemoveValidationAnnotations) }} 4 | apiVersion: apiextensions.k8s.io/v1 5 | kind: CustomResourceDefinition 6 | metadata: 7 | name: "venaficonnections.jetstack.io" 8 | {{- if .Values.crds.keep }} 9 | annotations: 10 | # This annotation prevents the CRD from being pruned by Helm when this chart 11 | # is deleted. 12 | helm.sh/resource-policy: keep 13 | {{- end }} 14 | labels: 15 | {{- include "venafi-connection.labels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | {{- if .Values.authentication.venafiConnection.enabled }} 2 | - Check the VenafiConnection exists: "{{ .Values.authentication.venafiConnection.namespace }}/{{ .Values.authentication.venafiConnection.name }}" 3 | > kubectl get VenafiConnection -n {{ .Values.authentication.venafiConnection.namespace }} {{ .Values.authentication.venafiConnection.name }} 4 | {{- else }} 5 | - Check the credentials Secret exists: "{{ .Values.authentication.secretName }}" 6 | > kubectl get secret -n {{ .Release.Namespace }} {{ .Values.authentication.secretName }} 7 | {{- end }} 8 | - Check the application is running: 9 | > kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} 10 | 11 | - Check the application logs for successful connection to the platform: 12 | > kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} 13 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "venafi-kubernetes-agent.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 "venafi-kubernetes-agent.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" $name .Release.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 "venafi-kubernetes-agent.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "venafi-kubernetes-agent.labels" -}} 37 | helm.sh/chart: {{ include "venafi-kubernetes-agent.chart" . }} 38 | {{ include "venafi-kubernetes-agent.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 "venafi-kubernetes-agent.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "venafi-kubernetes-agent.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 "venafi-kubernetes-agent.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "venafi-kubernetes-agent.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/templates/_venafi-connection.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Create chart name and version as used by the chart label. 3 | */}} 4 | {{- define "venafi-connection.chart" -}} 5 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Common labels 10 | */}} 11 | {{- define "venafi-connection.labels" -}} 12 | helm.sh/chart: {{ include "venafi-connection.chart" . }} 13 | {{ include "venafi-connection.selectorLabels" . }} 14 | {{- if .Chart.AppVersion }} 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | {{- end }} 17 | app.kubernetes.io/managed-by: {{ .Release.Service }} 18 | {{- end }} 19 | 20 | {{/* 21 | Selector labels 22 | */}} 23 | {{- define "venafi-connection.selectorLabels" -}} 24 | app.kubernetes.io/name: "venafi-connection" 25 | app.kubernetes.io/instance: {{ .Release.Name }} 26 | {{- end }} 27 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/templates/poddisruptionbudget.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.podDisruptionBudget.enabled }} 2 | apiVersion: policy/v1 3 | kind: PodDisruptionBudget 4 | metadata: 5 | name: {{ include "venafi-kubernetes-agent.fullname" . }} 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "venafi-kubernetes-agent.labels" . | nindent 4 }} 9 | spec: 10 | selector: 11 | matchLabels: 12 | {{- include "venafi-kubernetes-agent.selectorLabels" . | nindent 6 }} 13 | 14 | {{- if not (or (hasKey .Values.podDisruptionBudget "minAvailable") (hasKey .Values.podDisruptionBudget "maxUnavailable")) }} 15 | minAvailable: 1 # Default value because minAvailable and maxUnavailable are not set 16 | {{- end }} 17 | {{- if hasKey .Values.podDisruptionBudget "minAvailable" }} 18 | minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} 19 | {{- end }} 20 | {{- if hasKey .Values.podDisruptionBudget "maxUnavailable" }} 21 | maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} 22 | {{- end }} 23 | {{- end }} 24 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/templates/podmonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.metrics.enabled .Values.metrics.podmonitor.enabled }} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: PodMonitor 4 | metadata: 5 | name: {{ include "venafi-kubernetes-agent.fullname" . }} 6 | {{- if .Values.metrics.podmonitor.namespace }} 7 | namespace: {{ .Values.metrics.podmonitor.namespace }} 8 | {{- else }} 9 | namespace: {{ .Release.Namespace | quote }} 10 | {{- end }} 11 | labels: 12 | {{- include "venafi-kubernetes-agent.labels" . | nindent 4 }} 13 | prometheus: {{ .Values.metrics.podmonitor.prometheusInstance }} 14 | {{- with .Values.metrics.podmonitor.labels }} 15 | {{- toYaml . | nindent 4 }} 16 | {{- end }} 17 | {{- with .Values.metrics.podmonitor.annotations }} 18 | annotations: 19 | {{- toYaml . | nindent 4 }} 20 | {{- end }} 21 | spec: 22 | jobLabel: {{ include "venafi-kubernetes-agent.fullname" . }} 23 | selector: 24 | matchLabels: 25 | {{- include "venafi-kubernetes-agent.selectorLabels" . | nindent 6 }} 26 | {{- if .Values.metrics.podmonitor.namespace }} 27 | namespaceSelector: 28 | matchNames: 29 | - {{ .Release.Namespace | quote }} 30 | {{- end }} 31 | podMetricsEndpoints: 32 | - port: http-metrics 33 | path: /metrics 34 | interval: {{ .Values.metrics.podmonitor.interval }} 35 | scrapeTimeout: {{ .Values.metrics.podmonitor.scrapeTimeout }} 36 | honorLabels: {{ .Values.metrics.podmonitor.honorLabels }} 37 | {{- with .Values.metrics.podmonitor.endpointAdditionalProperties }} 38 | {{- toYaml . | nindent 4 }} 39 | {{- end }} 40 | {{- end }} 41 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "venafi-kubernetes-agent.serviceAccountName" . }} 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "venafi-kubernetes-agent.labels" . | nindent 4 }} 9 | {{- with .Values.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/templates/venafi-connection-rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.crds.venafiConnection.include }} 2 | # The 'venafi-connection' service account is used by multiple 3 | # controllers. When configuring which resources a VenafiConnection 4 | # can access, the RBAC rules you create manually must point to this SA. 5 | apiVersion: v1 6 | kind: ServiceAccount 7 | metadata: 8 | name: venafi-connection 9 | namespace: {{ $.Release.Namespace | quote }} 10 | labels: 11 | {{- include "venafi-connection.labels" $ | nindent 4 }} 12 | --- 13 | apiVersion: rbac.authorization.k8s.io/v1 14 | kind: ClusterRole 15 | metadata: 16 | name: venafi-connection-role 17 | labels: 18 | {{- include "venafi-connection.labels" $ | nindent 4 }} 19 | rules: 20 | - apiGroups: [ "" ] 21 | resources: [ "namespaces" ] 22 | verbs: [ "get", "list", "watch" ] 23 | 24 | - apiGroups: [ "jetstack.io" ] 25 | resources: [ "venaficonnections" ] 26 | verbs: [ "get", "list", "watch" ] 27 | 28 | - apiGroups: [ "jetstack.io" ] 29 | resources: [ "venaficonnections/status" ] 30 | verbs: [ "get", "patch" ] 31 | --- 32 | apiVersion: rbac.authorization.k8s.io/v1 33 | kind: ClusterRoleBinding 34 | metadata: 35 | name: venafi-connection-rolebinding 36 | labels: 37 | {{- include "venafi-connection.labels" $ | nindent 4 }} 38 | roleRef: 39 | apiGroup: rbac.authorization.k8s.io 40 | kind: ClusterRole 41 | name: venafi-connection-role 42 | subjects: 43 | - kind: ServiceAccount 44 | name: venafi-connection 45 | namespace: {{ $.Release.Namespace | quote }} 46 | {{- end }} 47 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/templates/venafi-rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.authentication.venafiConnection.enabled }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: venafi-kubernetes-agent-impersonate-role 6 | namespace: {{ $.Release.Namespace | quote }} 7 | labels: 8 | {{- include "venafi-kubernetes-agent.labels" . | nindent 4 }} 9 | rules: 10 | - apiGroups: [ "" ] 11 | resources: [ "serviceaccounts" ] 12 | verbs: [ "impersonate" ] 13 | resourceNames: [ "venafi-connection" ] 14 | --- 15 | apiVersion: rbac.authorization.k8s.io/v1 16 | kind: RoleBinding 17 | metadata: 18 | name: venafi-kubernetes-agent-impersonate-rolebinding 19 | namespace: {{ $.Release.Namespace | quote }} 20 | labels: 21 | {{- include "venafi-kubernetes-agent.labels" . | nindent 4 }} 22 | roleRef: 23 | apiGroup: rbac.authorization.k8s.io 24 | kind: Role 25 | name: venafi-kubernetes-agent-impersonate-role 26 | subjects: 27 | - kind: ServiceAccount 28 | name: {{ include "venafi-kubernetes-agent.serviceAccountName" . }} 29 | namespace: {{ $.Release.Namespace | quote }} 30 | {{- end }} 31 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/tests/deployment_test.yaml: -------------------------------------------------------------------------------- 1 | suite: test deployment 2 | templates: 3 | - deployment.yaml 4 | 5 | tests: 6 | # Basic checks on deployment 7 | - it: templates as expected 8 | set: 9 | image.tag: latest 10 | config.clientId: "00000000-0000-0000-0000-000000000000" 11 | template: deployment.yaml 12 | asserts: 13 | - isKind: 14 | of: Deployment 15 | # Validate name matches 16 | - matchRegex: 17 | path: metadata.name 18 | pattern: ^venafi-kubernetes-agent-* 19 | # Check is latest is set as tag that it uses that tag 20 | - equal: 21 | path: spec.template.spec.containers[0].image 22 | value: registry.venafi.cloud/venafi-agent/venafi-agent:latest 23 | 24 | # Check naming works with nameOverride 25 | - it: Deployment name is set when nameOverride is used 26 | set: 27 | nameOverride: example 28 | template: deployment.yaml 29 | asserts: 30 | - isKind: 31 | of: Deployment 32 | - matchRegex: 33 | path: metadata.name 34 | pattern: ^example-RELEASE-NAME$ 35 | 36 | # Check similar with fullnameOverride 37 | - it: Deployment name is set when fullnameOverride is used 38 | set: 39 | config.clientId: "00000000-0000-0000-0000-000000000000" 40 | fullnameOverride: example 41 | template: deployment.yaml 42 | asserts: 43 | - isKind: 44 | of: Deployment 45 | - equal: 46 | path: metadata.name 47 | value: example 48 | 49 | # Checking extraArgs are passed 50 | - it: Extra Args passed in a valid format when supplied 51 | set: 52 | config.clientId: "00000000-0000-0000-0000-000000000000" 53 | extraArgs: ["--strict", "--one-shot"] 54 | template: deployment.yaml 55 | asserts: 56 | - isKind: 57 | of: Deployment 58 | - contains: 59 | path: spec.template.spec.containers[0].args 60 | content: --strict 61 | - contains: 62 | path: spec.template.spec.containers[0].args 63 | content: --one-shot 64 | 65 | # Check command is present when configured 66 | - it: Command passes to deployment manifest 67 | set: 68 | config.clientId: "00000000-0000-0000-0000-000000000000" 69 | command: ["notpreflight"] 70 | template: deployment.yaml 71 | asserts: 72 | - isKind: 73 | of: Deployment 74 | - contains: 75 | path: spec.template.spec.containers[0].command 76 | content: notpreflight 77 | 78 | # Check the volumes and volumeMounts works correctly 79 | - it: Volumes and VolumeMounts added correctly 80 | set: 81 | config.organisation: test_org 82 | config.cluster: test_cluster 83 | values: 84 | - ./values/custom-volumes.yaml 85 | asserts: 86 | - isKind: 87 | of: Deployment 88 | - equal: 89 | # In template this comes after credentials and agent config volumeMounts 90 | path: spec.template.spec.containers[0].volumeMounts[?(@.name == "cabundle")] 91 | value: 92 | name: cabundle 93 | mountPath: /etc/ssl/certs/ca-certificates.crt 94 | subPath: ca-certificates.crt 95 | readOnly: true 96 | - equal: 97 | path: spec.template.spec.volumes[?(@.name == "cabundle")].configMap 98 | value: 99 | name: cabundle 100 | optional: false 101 | defaultMode: 0644 102 | items: 103 | - key: cabundle 104 | path: ca-certificates.crt 105 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/tests/values/custom-volumes.yaml: -------------------------------------------------------------------------------- 1 | volumes: 2 | - name: cabundle 3 | configMap: 4 | name: cabundle 5 | optional: false 6 | defaultMode: 0644 7 | items: 8 | - key: cabundle 9 | path: ca-certificates.crt 10 | 11 | volumeMounts: 12 | - name: cabundle 13 | mountPath: /etc/ssl/certs/ca-certificates.crt 14 | subPath: ca-certificates.crt 15 | readOnly: true 16 | -------------------------------------------------------------------------------- /deploy/charts/venafi-kubernetes-agent/values.linter.exceptions: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetstack/jetstack-secure/8c2091c680d8143c5f1c6993147600838eafaac7/deploy/charts/venafi-kubernetes-agent/values.linter.exceptions -------------------------------------------------------------------------------- /docs/datagatherers/aks.md: -------------------------------------------------------------------------------- 1 | # AKS Data Gatherer 2 | 3 | The AKS *data gatherer* fetches information about a cluster from the Azure 4 | Kubernetes Service API. 5 | 6 | ## Data 7 | 8 | Preflight collects data about clusters. The fields included here can be found 9 | [here](https://docs.microsoft.com/en-us/rest/api/aks/managedclusters/get). 10 | 11 | ## Configuration 12 | 13 | To use the AKS data gatherer add an `aks` entry to the `data-gatherers` 14 | configuration. For example: 15 | 16 | ``` 17 | data-gatherers: 18 | - kind: "aks" 19 | name: "aks" 20 | config: 21 | resource-group: example 22 | cluster-name: my-aks-cluster 23 | credentials-path: /tmp/credentials.json 24 | ``` 25 | 26 | The `aks` configuration contains the following fields: 27 | 28 | - `resource-group`: The Azure resource group where the cluster is located. 29 | - `cluster-name`: The name of your AKS cluster. 30 | - `credentials`: The path to a file containing credentials for Azure APIs. 31 | 32 | ## Permissions 33 | 34 | You must [create](https://docs.microsoft.com/en-us/azure/aks/kubernetes-service-principal#manually-create-a-service-principal) 35 | a Service Principal and [link](https://docs.microsoft.com/en-us/azure/aks/kubernetes-service-principal#specify-a-service-principal-for-an-aks-cluster) 36 | a it to your AKS cluster. 37 | -------------------------------------------------------------------------------- /docs/datagatherers/eks.md: -------------------------------------------------------------------------------- 1 | # EKS Data Gatherer 2 | 3 | The EKS *data gatherer* fetches information about a cluster from the AWS 4 | Elastic Kubernetes Service API. 5 | 6 | ## Data 7 | 8 | Preflight collects data about clusters. The fields included here can be found 9 | [here](https://docs.aws.amazon.com/eks/latest/APIReference/API_Cluster.html). 10 | 11 | ## Configuration 12 | 13 | To use the EKS data gatherer add an `eks` entry to the `data-gatherers` 14 | configuration. For example: 15 | 16 | ``` 17 | data-gatherers: 18 | - kind: "eks" 19 | name: "eks" 20 | config: 21 | cluster-name: my-eks-cluster 22 | ``` 23 | 24 | The `eks` configuration contains the following fields: 25 | 26 | - `cluster-name`: The name of your EKS cluster. 27 | 28 | ## Permissions 29 | 30 | Example Policy: 31 | 32 | ```json 33 | { 34 | "Version": "2012-10-17", 35 | "Statement": [ 36 | { 37 | "Effect": "Allow", 38 | "Action": [ 39 | "eks:DescribeCluster", 40 | "eks:ListClusters" 41 | ], 42 | "Resource": "arn:aws:eks:*:111122223333:cluster/*" 43 | } 44 | ] 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/datagatherers/gke.md: -------------------------------------------------------------------------------- 1 | # GKE Data Gatherer 2 | 3 | The GKE *data gatherer* fetches information about a cluster from the Google 4 | Kubernetes Engine API. 5 | 6 | ## Data 7 | 8 | The output of the GKE data gatherer follows the format described in the 9 | [GKE API reference](https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1beta1/projects.locations.clusters#Cluster) 10 | and the [Go Docs](https://godoc.org/google.golang.org/api/container/v1#Cluster). 11 | 12 | It's comparable to the output from: 13 | 14 | ```bash 15 | gcloud container clusters describe my-cluster --format=json 16 | ``` 17 | 18 | ## Configuration 19 | 20 | To use the GKE data gatherer add a `gke` entry to the `data-gatherers` 21 | configuration. For example: 22 | 23 | ``` 24 | data-gatherers: 25 | - kind: "gke" 26 | name: "gke" 27 | config: 28 | # Path to a file containing the credentials. If empty, it will try to use 29 | # the SDK defaults (explained below) 30 | # credentials-path: /tmp/credentials.json 31 | cluster: 32 | project: my-gcp-project 33 | location: us-central1-a 34 | name: my-gke-cluster 35 | ``` 36 | 37 | The `gke` configuration contains the following fields: 38 | 39 | - `credentials-path`: *optional* The path to a file containing credentials for 40 | your cluster. 41 | - `cluster` 42 | - `name`: The name of your GKE cluster. 43 | - `project`: The ID of your Google Cloud Platform project. 44 | - `location`: The compute zone or region where your cluster is running. 45 | 46 | ## Permissions 47 | 48 | If a `credentials-path` file is not specified, Preflight will attempt to use 49 | Application Default Credentials or the metadata API (as per Google SDK default). 50 | 51 | If Preflight is running locally and the `gcloud` command is installed and 52 | configured, just run `gcloud auth application-default login` to set up 53 | Application Default Credentials. 54 | 55 | The `credentials` file path is useful if you want to configure a separate 56 | service account for Preflight to use to fetch GKE data. 57 | 58 | The user and service account must have the correct [IAM 59 | Roles](https://cloud.google.com/kubernetes-engine/docs/how-to/iam). 60 | Specifically it must have the `container.clusters.get` permission. This can be 61 | given with the _Kubernetes Engine Cluster Viewer_ role 62 | (`roles/container.clusterViewer`). 63 | 64 | ### Sample Terraform Configuration 65 | 66 | This can be used to create a GCP service account called `preflight` which is 67 | then bound to a custom role of the same name with the minimum required 68 | permissions. 69 | 70 | 71 | ```hcl 72 | terraform { 73 | required_version = "~> 0.12" 74 | } 75 | 76 | variable "project_id" { 77 | type = string 78 | description = "The ID of the project where the cluster Preflight is going to check is." 79 | } 80 | 81 | # https://www.terraform.io/docs/providers/google/index.html 82 | provider "google" { 83 | version = "2.5.1" 84 | project = var.project_id 85 | } 86 | 87 | # https://www.terraform.io/docs/providers/google/r/google_service_account.html 88 | resource "google_service_account" "preflight_agent_service_account" { 89 | project = var.project_id 90 | account_id = "preflight-agent" 91 | display_name = "Service account for Preflight Agent" 92 | } 93 | 94 | # https://www.terraform.io/docs/providers/google/r/google_project_iam_custom_role.html 95 | resource "google_project_iam_member" "preflight_agent_cluster_viewer" { 96 | project = var.project_id 97 | role = "roles/container.clusterViewer" # allows getting of credentials, all other permissions handled in k8s RBAC 98 | member = "serviceAccount:${google_service_account.preflight_agent_service_account.email}" 99 | } 100 | 101 | # if using workload identity in GKE, use the following binding to allow the 102 | # agent to use the service account 103 | resource "google_service_account_iam_binding" "preflight_agent_workload_identity" { 104 | service_account_id = google_service_account.preflight_agent_service_account.name 105 | role = "roles/iam.workloadIdentityUser" 106 | members = ["serviceAccount:${var.project_id}.svc.id.goog[preflight/agent]"] 107 | } 108 | ``` 109 | 110 | An annotation specifing the gcp service account must be added to the agent's k8s service account 111 | `iam.gke.io/gcp-service-account=@` 112 | -------------------------------------------------------------------------------- /docs/datagatherers/k8s-discovery.md: -------------------------------------------------------------------------------- 1 | # k8s-discovery 2 | 3 | This datagatherer uses the [DiscoveryClient](https://godoc.org/k8s.io/client-go/discovery#DiscoveryClient) 4 | to get API server version information. 5 | 6 | Include the following in your agent config: 7 | 8 | ``` 9 | data-gatherers: 10 | - kind: "k8s-discovery" 11 | name: "k8s-discovery" 12 | ``` 13 | 14 | or specify a kubeconfig file: 15 | 16 | ``` 17 | data-gatherers: 18 | - kind: "k8s-discovery" 19 | name: "k8s-discovery" 20 | config: 21 | kubeconfig: other_kube_config_path 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/datagatherers/k8s-dynamic.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Data Gatherer 2 | 3 | The Kubernetes dynamic data gatherer collects information about resources stored 4 | in the Kubernetes API. 5 | 6 | ## Data 7 | 8 | The data gathered depends on your configuration. Resources are selected based on 9 | their Group-Version-Kind identifiers, e.g.: 10 | 11 | * Core resources such as `Service`, use: `k8s/services.v1` 12 | * `Ingress`, use: `k8s/ingresses.v1beta1.networking.k8s.io` 13 | * Custom resources such as `Certificates`, use: 14 | `k8s/certificates.v1alpha2.cert-manager.io` 15 | 16 | To see an example of the data being gathered, using `k8s/services.v1` is 17 | comparable to the output from: 18 | 19 | ```bash 20 | kubectl get services --all-namespaces -o json 21 | ``` 22 | 23 | ## Configuration 24 | 25 | You can collect different resources using difference Group-Version-Kind as 26 | below: 27 | 28 | ```yaml 29 | data-gatherers: 30 | # basic usage 31 | - kind: "k8s-dynamic" 32 | name: "k8s/pods" 33 | config: 34 | resource-type: 35 | resource: pods 36 | version: v1 37 | 38 | # CRD usage 39 | - kind: "k8s-dynamic" 40 | name: "k8s/certificates.v1alpha2.cert-manager.io" 41 | config: 42 | resource-type: 43 | group: cert-manager.io 44 | version: v1alpha2 45 | resource: certificates 46 | 47 | # you might event want to gather resources from another cluster 48 | - kind: "k8s-dynamic" 49 | name: "k8s/pods" 50 | config: 51 | kubeconfig: other_kube_config_path 52 | ``` 53 | 54 | The `kubeconfig` field should point to your Kubernetes config file - this is 55 | typically found at `~/.kube/config`. Preflight will use the context that is 56 | active in that config file. 57 | 58 | ## Permissions 59 | 60 | The user or service account used by the Kubernetes config to authenticate with 61 | the Kubernetes API must have permission to perform `list` and `get` on the 62 | resource referenced in the `kind` for that datagatherer. 63 | 64 | There is an example `ClusterRole` and `ClusterRoleBinding` which can be found in 65 | [`./deployment/kubernetes/base/00-rbac.yaml`](./deployment/kubernetes/base/00-rbac.yaml). 66 | 67 | ## Secrets 68 | 69 | Secrets can be gathered using the following config: 70 | 71 | ```yaml 72 | - kind: "k8s-dynamic" 73 | name: "k8s/secrets" 74 | config: 75 | resource-type: 76 | version: v1 77 | resource: secrets 78 | ``` 79 | 80 | Before Secrets are sent to the Preflight backend, they are redacted so no secret data is transmitted. See [`fieldfilter.go`](./../../pkg/datagatherer/k8s/fieldfilter.go) to see the details of which fields are filteres and which ones are redacted. 81 | 82 | > **All resource other than Kubernetes Secrets are sent in full, so make sure that you don't store secret information on arbitrary resources.** 83 | 84 | 85 | ## Field Selectors 86 | 87 | You can use [field selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/#list-of-supported-fields) 88 | to include or exclude certain resources. 89 | For example, you can reduce the memory usage of the agent and reduce the load on the Kubernetes 90 | API server by omitting various common [Secret types](https://kubernetes.io/docs/concepts/configuration/secret/#secret-types) 91 | when listing Secrets. 92 | 93 | ```yaml 94 | - kind: "k8s-dynamic" 95 | name: "k8s/secrets" 96 | config: 97 | resource-type: 98 | version: v1 99 | resource: secrets 100 | field-selectors: 101 | - type!=kubernetes.io/service-account-token 102 | - type!=kubernetes.io/dockercfg 103 | - type!=kubernetes.io/dockerconfigjson 104 | - type!=kubernetes.io/basic-auth 105 | - type!=kubernetes.io/ssh-auth, 106 | - type!=bootstrap.kubernetes.io/token 107 | - type!=helm.sh/release.v1 108 | ``` 109 | -------------------------------------------------------------------------------- /docs/datagatherers/local.md: -------------------------------------------------------------------------------- 1 | # Local Data Gatherer 2 | 3 | The Local data gatherer is intended to be used for reading data for evaluation 4 | from the local file system. It can also be used for 'stubbing' remote data 5 | sources when testing other data gatherers. 6 | 7 | ## Configuration 8 | 9 | Stubbing another datagatherer for testing: 10 | 11 | ```yaml 12 | data-gatherers: 13 | - kind: "gke" 14 | name: "gke" 15 | config: 16 | # fetch from local path instead of GKE 17 | data-path: ./examples/data/example.json 18 | ``` 19 | 20 | Loading other data as 'local': 21 | 22 | ```yaml 23 | data-gatherers: 24 | - kind: "local" 25 | name: "local" 26 | config: 27 | data-path: ./examples/data/example.json 28 | ``` 29 | 30 | ## Data 31 | 32 | Data is gathered from the local file system - whatever is read from the file is 33 | used. 34 | 35 | ## Permissions 36 | 37 | Permissions to read the local path. 38 | -------------------------------------------------------------------------------- /docs/guides/cosign/guide.md: -------------------------------------------------------------------------------- 1 | # Cosign 2 | 3 | > Note: the ['keyless' signing feature](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) 4 | > of `cosign` used here is currently classified as 'experimental' 5 | 6 | The jetstack-secure agent container image is signed using 7 | [`cosign`](https://github.com/sigstore/cosign). 8 | 9 | An attestation is attached which satisfies the requirements of 10 | [SLSA 1](https://slsa.dev/spec/v0.1/requirements) and a 11 | [CycloneDX Software Bill of Materials](https://cyclonedx.org/) is also provided 12 | that details the dependencies of the image. 13 | 14 | This document outlines how to verify the signature, attestation and download 15 | the SBOM with the `cosign` CLI. 16 | 17 | ## Signature 18 | 19 | To verify the container image signature: 20 | 21 | 1. Ensure `cosign` is installed 22 | 2. Configure the signature repository and enable experimental features: 23 | 24 | ``` 25 | export COSIGN_REPOSITORY=ghcr.io/jetstack/jetstack-secure/cosign 26 | export COSIGN_EXPERIMENTAL=1 27 | ``` 28 | 29 | 3. Verify the image 30 | 31 | ``` 32 | cosign verify --cert-oidc-issuer https://token.actions.githubusercontent.com quay.io/jetstack/preflight:latest 33 | ``` 34 | 35 | If the container was properly signed then the command should exit successfully. 36 | 37 | The `Subject` in the output should be 38 | `https://github.com/jetstack/jetstack-secure/.github/workflows/release-master.yaml@`, 39 | where `` is either the `master` branch or a release tag, i.e: 40 | 41 | - `refs/heads/master` 42 | - `refs/tags/v0.1.35` 43 | 44 | ## SLSA Provenance Attestation 45 | 46 | To verify and view the SLSA provenance attestation: 47 | 48 | 1. Ensure `cosign` is installed 49 | 2. Configure the signature repository and enable experimental features: 50 | 51 | ``` 52 | export COSIGN_REPOSITORY=ghcr.io/jetstack/jetstack-secure/cosign 53 | export COSIGN_EXPERIMENTAL=1 54 | ``` 55 | 56 | 3. Verify and decode the attestation payload: 57 | 58 | ``` 59 | cosign verify-attestation --cert-oidc-issuer https://token.actions.githubusercontent.com quay.io/jetstack/preflight:latest | tail -n 1 | jq -r .payload | base64 -d | jq -r . 60 | ``` 61 | 62 | ## Software Bill of Materials (SBOM) 63 | 64 | To verify and download the SBOM: 65 | 66 | 1. Ensure `cosign` is installed 67 | 2. Configure the signature repository and enable experimental features: 68 | 69 | ``` 70 | export COSIGN_REPOSITORY=ghcr.io/jetstack/jetstack-secure/cosign 71 | export COSIGN_EXPERIMENTAL=1 72 | ``` 73 | 74 | 3. Verify the SBOM 75 | 76 | ``` 77 | cosign verify --attachment sbom --cert-oidc-issuer https://token.actions.githubusercontent.com quay.io/jetstack/preflight:latest 78 | ``` 79 | 80 | If the SBOM was properly signed then the command should exit successfully. 81 | 82 | The `Subject` in the output should be 83 | `https://github.com/jetstack/jetstack-secure/.github/workflows/release-master.yaml@`, 84 | where `` is either the `master` branch or a release tag, i.e: 85 | 86 | - `refs/heads/master` 87 | - `refs/tags/v0.1.35` 88 | 89 | 4. Download the SBOM 90 | 91 | ``` 92 | cosign download sbom quay.io/jetstack/preflight:latest > bom.xml 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/guides/gke_local/guide.md: -------------------------------------------------------------------------------- 1 | # How to run Preflight with the GKE package 2 | 3 | This doc guides you on how to run preflight-agent locally to scan a GKE installation. 4 | 5 | As a result, you will get a new cluster in your Preflight account showing reports with the GKE package. 6 | 7 | It executes the agent locally because this guide is intented to be an interative demonstration, but the agent could be installed in-cluster as well. Take a look at the [GKE data-gatherer manual](../../datagatherers/gke.md) for further details on how to configure authentication in that scenario. 8 | 9 | ## Requisites 10 | 11 | - A GKE cluster. 12 | - The `gcloud` CLI tool working locally on your computer. 13 | - `gcloud` is logged in to an account with permissions to read cluster and GKE state in the project where your cluster is running. 14 | - You have already run `gcloud container clusters get-credentials [cluster] ...` to configure your kubeconfig. 15 | 16 | ## Get the preflight cli 17 | 18 | Go to [the releases page](https://github.com/jetstack/preflight/releases) and download the binary of the latest stable release for your platform (`v0.1.15` at the moment of writing this). 19 | 20 | ![release](./img/release.png) 21 | 22 | For instance: 23 | 24 | ``` 25 | curl -L https://github.com/jetstack/preflight/releases/download/v0.1.15/preflight-linux-amd64 > preflight 26 | chmod +x ./preflight 27 | ``` 28 | 29 | Then run this to make sure you downloaded the right thing: 30 | 31 | ``` 32 | ./preflight version 33 | ``` 34 | 35 | ## Access Preflight 36 | 37 | > At the moment, Preflight is in private beta. If you are interested, please [email us](mailto:preflight-maintainers@jetstack.io?subject=Preflight%20Beta%20Access) and we will grant to access to the beta program. 38 | 39 | If you have an account, you will have an access token. 40 | 41 | Enter [preflight.jetstack.io/login](https://preflight.jetstack.io/login) and access with your token. 42 | 43 | You will be presented with the list of cluster in your organization. 44 | 45 | ## Add a new cluster and start agent 46 | 47 | Click in "Add Cluster". 48 | 49 | Choose a name for your cluster (e.g. `my-gke-cluster`) and click the accept button. 50 | 51 | Then you will be presented with instructions to install the agent in a cluster. 52 | 53 | Since you are going to run the agent locally instead of installing it in a cluster, you must copy the agent token and craft a config file manually. 54 | 55 | You can extract the agent token from the installation command, as shown in the picture. 56 | 57 | ![token](./img/select_token.png) 58 | 59 | Then you can use this snipped as template for your configuration file and replace the agent token where it corresponds. 60 | 61 | Also, change `cluster.project`, `cluster.location`, `cluster.name` accordingly so they point to your GKE cluster (`gcloud container clusters list` if you don't remember those values.). 62 | 63 | If you don't specify `credentials`, it will try to use _Gcloud Application Default Credentials_. You can run `gcloud auth application-default login` to make sure your _Application Default Credentials_ are configured. 64 | 65 | ```yaml 66 | # config.yaml 67 | 68 | schedule: "* * * *" 69 | token: "" 70 | endpoint: 71 | protocol: https 72 | host: "preflight.jetstack.io" 73 | path: "/api/v1/datareadings" 74 | data-gatherers: 75 | - kind: "gke" 76 | name: "gke" 77 | config: 78 | cluster: 79 | project: my-gcp-project 80 | location: us-central1-a 81 | name: my-gke-cluster 82 | # Path to a file containing the credentials. If empty, it will try to use 83 | # the SDK defaults 84 | # credentials: /tmp/credentials.json 85 | ``` 86 | 87 | Save the file as `config.yaml`. 88 | 89 | Now you can run the Preflight agent: 90 | 91 | ``` 92 | ./preflight agent -c ./config.yaml 93 | ``` 94 | 95 | You should see in the log that it is sending data to the server periodically. 96 | 97 | If you go back to the add cluster wizard and click "The agent it ready" 98 | 99 | ![ready](./img/ready.png) 100 | 101 | ## See reports 102 | 103 | If you go to the clusters view, you will see the new cluster is there: 104 | 105 | ![cluster](./img/cluster.png) 106 | -------------------------------------------------------------------------------- /docs/guides/gke_local/img/cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetstack/jetstack-secure/8c2091c680d8143c5f1c6993147600838eafaac7/docs/guides/gke_local/img/cluster.png -------------------------------------------------------------------------------- /docs/guides/gke_local/img/ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetstack/jetstack-secure/8c2091c680d8143c5f1c6993147600838eafaac7/docs/guides/gke_local/img/ready.png -------------------------------------------------------------------------------- /docs/guides/gke_local/img/release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetstack/jetstack-secure/8c2091c680d8143c5f1c6993147600838eafaac7/docs/guides/gke_local/img/release.png -------------------------------------------------------------------------------- /docs/guides/gke_local/img/select_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetstack/jetstack-secure/8c2091c680d8143c5f1c6993147600838eafaac7/docs/guides/gke_local/img/select_token.png -------------------------------------------------------------------------------- /docs/images/azblob_keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetstack/jetstack-secure/8c2091c680d8143c5f1c6993147600838eafaac7/docs/images/azblob_keys.png -------------------------------------------------------------------------------- /docs/images/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetstack/jetstack-secure/8c2091c680d8143c5f1c6993147600838eafaac7/docs/images/js.png -------------------------------------------------------------------------------- /docs/images/preflight_package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jetstack/jetstack-secure/8c2091c680d8143c5f1c6993147600838eafaac7/docs/images/preflight_package.png -------------------------------------------------------------------------------- /examples/cert-manager-agent.yaml: -------------------------------------------------------------------------------- 1 | organization_id: "my-organization" 2 | cluster_id: "my_cluster" 3 | schedule: "* * * *" 4 | token: xxxx 5 | endpoint: 6 | protocol: https 7 | host: "preflight.jetstack.io" 8 | path: "/api/v1/datareadings" 9 | data-gatherers: 10 | - kind: "k8s-dynamic" 11 | name: "k8s/secrets.v1" 12 | config: 13 | resource-type: 14 | version: v1 15 | resource: secrets 16 | - kind: "k8s-dynamic" 17 | name: "k8s/certificates.v1.cert-manager.io" 18 | config: 19 | resource-type: 20 | group: cert-manager.io 21 | version: v1 22 | resource: certificates 23 | - kind: "k8s-dynamic" 24 | name: "k8s/ingresses.v1.networking.k8s.io" 25 | config: 26 | resource-type: 27 | group: networking.k8s.io 28 | version: v1 29 | resource: ingresses 30 | - kind: "k8s-dynamic" 31 | name: "k8s/certificaterequests.v1.cert-manager.io" 32 | config: 33 | resource-type: 34 | group: cert-manager.io 35 | version: v1 36 | resource: certificaterequests 37 | -------------------------------------------------------------------------------- /examples/echo/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "sampledata": 1 3 | } 4 | -------------------------------------------------------------------------------- /examples/echo/example2.json: -------------------------------------------------------------------------------- 1 | { 2 | "sampledata": 1 3 | } -------------------------------------------------------------------------------- /examples/one-shot-secret.yaml: -------------------------------------------------------------------------------- 1 | # one-shot-secret.yaml 2 | # 3 | # An example configuration file which can be used for local testing. 4 | # It gathers only secrets and it does not attempt to upload to Venafi. 5 | # For example: 6 | # 7 | # builds/preflight agent \ 8 | # --agent-config-file examples/one-shot-secret.yaml \ 9 | # --one-shot \ 10 | # --output-path output.json 11 | # 12 | organization_id: "my-organization" 13 | cluster_id: "my_cluster" 14 | period: 1m 15 | data-gatherers: 16 | - kind: "k8s-dynamic" 17 | name: "k8s/secrets" 18 | config: 19 | resource-type: 20 | version: v1 21 | resource: secrets 22 | field-selectors: 23 | - type!=kubernetes.io/service-account-token 24 | - type!=kubernetes.io/dockercfg 25 | - type!=kubernetes.io/dockerconfigjson 26 | - type!=kubernetes.io/basic-auth 27 | - type!=kubernetes.io/ssh-auth, 28 | - type!=bootstrap.kubernetes.io/token 29 | - type!=helm.sh/release.v1 30 | -------------------------------------------------------------------------------- /hack/e2e/application-team-1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: team-1 5 | --- 6 | apiVersion: policy.cert-manager.io/v1alpha1 7 | kind: CertificateRequestPolicy 8 | metadata: 9 | name: team-1 10 | spec: 11 | allowed: 12 | commonName: 13 | value: '*' 14 | dnsNames: 15 | values: 16 | - '*' 17 | subject: 18 | countries: 19 | values: 20 | - '*' 21 | localities: 22 | values: 23 | - '*' 24 | organizationalUnits: 25 | values: 26 | - '*' 27 | organizations: 28 | values: 29 | - '*' 30 | postalCodes: 31 | values: 32 | - '*' 33 | provinces: 34 | values: 35 | - '*' 36 | serialNumber: 37 | value: '*' 38 | streetAddresses: 39 | values: 40 | - '*' 41 | usages: 42 | - digital signature 43 | - key encipherment 44 | - server auth 45 | - client auth 46 | plugins: 47 | venafi: 48 | values: 49 | venafiConnectionName: venafi-components 50 | zone: ${VEN_ZONE} 51 | selector: 52 | issuerRef: 53 | group: jetstack.io 54 | kind: VenafiIssuer 55 | name: venafi-cloud 56 | namespace: 57 | matchNames: 58 | - team-1 59 | --- 60 | apiVersion: jetstack.io/v1alpha1 61 | kind: VenafiIssuer 62 | metadata: 63 | name: venafi-cloud 64 | namespace: team-1 65 | spec: 66 | certificateNameExpression: request.namespace + "_" + request.name 67 | venafiConnectionName: venafi-components 68 | venafiConnectionNamespace: venafi 69 | zone: ${VEN_ZONE} 70 | --- 71 | apiVersion: cert-manager.io/v1 72 | kind: Certificate 73 | metadata: 74 | name: app-0 75 | namespace: team-1 76 | spec: 77 | commonName: app-0.team-1 78 | duration: 720h0m0s 79 | renewBefore: 719h0m0s 80 | issuerRef: 81 | group: jetstack.io 82 | kind: VenafiIssuer 83 | name: venafi-cloud 84 | privateKey: 85 | algorithm: RSA 86 | rotationPolicy: Always 87 | size: 2048 88 | revisionHistoryLimit: 1 89 | secretName: app-0 90 | --- 91 | apiVersion: rbac.authorization.k8s.io/v1 92 | kind: Role 93 | metadata: 94 | name: cert-manager-policy:allow 95 | namespace: team-1 96 | rules: 97 | - apiGroups: ["policy.cert-manager.io"] 98 | resources: ["certificaterequestpolicies"] 99 | verbs: ["use"] 100 | --- 101 | apiVersion: rbac.authorization.k8s.io/v1 102 | kind: RoleBinding 103 | metadata: 104 | name: cert-manager-policy:allow 105 | namespace: team-1 106 | roleRef: 107 | apiGroup: rbac.authorization.k8s.io 108 | kind: Role 109 | name: cert-manager-policy:allow 110 | subjects: 111 | - kind: Group 112 | name: system:authenticated 113 | apiGroup: rbac.authorization.k8s.io 114 | -------------------------------------------------------------------------------- /hack/e2e/values.venafi-kubernetes-agent.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | clusterName: venafi-kubernetes-agent-e2e 3 | clusterDescription: | 4 | A kind cluster used for testing the venafi-kubernetes-agent. 5 | excludeAnnotationKeysRegex: ['^kapp\.k14s\.io/original.*'] 6 | 7 | authentication: 8 | venafiConnection: 9 | enabled: true 10 | 11 | extraArgs: 12 | - --logging-format=json 13 | - --log-level=6 14 | -------------------------------------------------------------------------------- /hack/e2e/venafi-components.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: venafi-components 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: Role 8 | metadata: 9 | name: venafi-components-create-token 10 | rules: 11 | - apiGroups: [ "" ] 12 | resources: [ "serviceaccounts/token" ] 13 | verbs: [ "create" ] 14 | resourceNames: [ "venafi-components" ] 15 | --- 16 | apiVersion: rbac.authorization.k8s.io/v1 17 | kind: RoleBinding 18 | metadata: 19 | name: venafi-components-create-token 20 | roleRef: 21 | apiGroup: rbac.authorization.k8s.io 22 | kind: Role 23 | name: venafi-components-create-token 24 | subjects: 25 | - kind: ServiceAccount 26 | name: venafi-connection 27 | namespace: venafi 28 | -------------------------------------------------------------------------------- /klone.yaml: -------------------------------------------------------------------------------- 1 | # This klone.yaml file describes the Makefile modules and versions that are 2 | # cloned into the "make/_shared" folder. These modules are dynamically imported 3 | # by the root Makefile. The "make upgrade-klone" target can be used to pull 4 | # the latest version from the upstream repositories (using the repo_ref value). 5 | # 6 | # More info can be found here: https://github.com/cert-manager/makefile-modules 7 | 8 | targets: 9 | make/_shared: 10 | - folder_name: generate-verify 11 | repo_url: https://github.com/cert-manager/makefile-modules.git 12 | repo_ref: main 13 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 14 | repo_path: modules/generate-verify 15 | - folder_name: go 16 | repo_url: https://github.com/cert-manager/makefile-modules.git 17 | repo_ref: main 18 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 19 | repo_path: modules/go 20 | - folder_name: helm 21 | repo_url: https://github.com/cert-manager/makefile-modules.git 22 | repo_ref: main 23 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 24 | repo_path: modules/helm 25 | - folder_name: help 26 | repo_url: https://github.com/cert-manager/makefile-modules.git 27 | repo_ref: main 28 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 29 | repo_path: modules/help 30 | - folder_name: kind 31 | repo_url: https://github.com/cert-manager/makefile-modules.git 32 | repo_ref: main 33 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 34 | repo_path: modules/kind 35 | - folder_name: klone 36 | repo_url: https://github.com/cert-manager/makefile-modules.git 37 | repo_ref: main 38 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 39 | repo_path: modules/klone 40 | - folder_name: licenses 41 | repo_url: https://github.com/cert-manager/makefile-modules.git 42 | repo_ref: main 43 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 44 | repo_path: modules/licenses 45 | - folder_name: oci-build 46 | repo_url: https://github.com/cert-manager/makefile-modules.git 47 | repo_ref: main 48 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 49 | repo_path: modules/oci-build 50 | - folder_name: oci-publish 51 | repo_url: https://github.com/cert-manager/makefile-modules.git 52 | repo_ref: main 53 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 54 | repo_path: modules/oci-publish 55 | - folder_name: repository-base 56 | repo_url: https://github.com/cert-manager/makefile-modules.git 57 | repo_ref: main 58 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 59 | repo_path: modules/repository-base 60 | - folder_name: tools 61 | repo_url: https://github.com/cert-manager/makefile-modules.git 62 | repo_ref: main 63 | repo_hash: 5c3266f17637fbd1d4af1191b34674991e2160ab 64 | repo_path: modules/tools 65 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/jetstack/preflight/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /make/00_mod.mk: -------------------------------------------------------------------------------- 1 | repo_name := github.com/jetstack/preflight 2 | 3 | license_ignore := gitlab.com/venafi,github.com/jetstack 4 | 5 | kind_cluster_name := preflight 6 | kind_cluster_config := $(bin_dir)/scratch/kind_cluster.yaml 7 | 8 | build_names := preflight 9 | 10 | go_preflight_main_dir := . 11 | go_preflight_mod_dir := . 12 | go_preflight_ldflags := \ 13 | -X $(repo_name)/pkg/version.PreflightVersion=$(VERSION) \ 14 | -X $(repo_name)/pkg/version.Commit=$(GITCOMMIT) \ 15 | -X $(repo_name)/pkg/version.BuildDate=$(shell date "+%F-%T-%Z") \ 16 | -X $(repo_name)/pkg/client.ClientID=k3TrDbfLhCgnpAbOiiT2kIE1AbovKzjo \ 17 | -X $(repo_name)/pkg/client.ClientSecret=f39w_3KT9Vp0VhzcPzvh-uVbudzqCFmHER3Huj0dvHgJwVrjxsoOQPIw_1SDiCfa \ 18 | -X $(repo_name)/pkg/client.AuthServerDomain=auth.jetstack.io 19 | 20 | oci_preflight_base_image_flavor := static 21 | oci_preflight_image_name := quay.io/jetstack/venafi-agent 22 | oci_preflight_image_tag := $(VERSION) 23 | oci_preflight_image_name_development := jetstack.local/venafi-agent 24 | 25 | # Annotations are the standardised set of annotations we set on every component we publish 26 | oci_preflight_build_args := \ 27 | --image-annotation="org.opencontainers.image.vendor"="CyberArk Software Ltd." \ 28 | --image-annotation="org.opencontainers.image.licenses"="EULA - https://www.cyberark.com/contract-terms/" \ 29 | --image-annotation="org.opencontainers.image.authors"="support@venafi.cloud" \ 30 | --image-annotation="org.opencontainers.image.title"="Venafi Kubernetes Agent" \ 31 | --image-annotation="org.opencontainers.image.description"="Gathers machine identity data from Kubernetes clusters." \ 32 | --image-annotation="org.opencontainers.image.url"="https://www.cyberark.com/products/certificate-manager-for-kubernetes/" \ 33 | --image-annotation="org.opencontainers.image.documentation"="https://docs.venafi.cloud/vaas/k8s-components/c-tlspk-agent-overview/" \ 34 | --image-annotation="org.opencontainers.image.version"="$(VERSION)" \ 35 | --image-annotation="org.opencontainers.image.revision"="$(GITCOMMIT)" 36 | 37 | deploy_name := venafi-kubernetes-agent 38 | deploy_namespace := venafi 39 | 40 | helm_chart_source_dir := deploy/charts/venafi-kubernetes-agent 41 | helm_chart_image_name := quay.io/jetstack/charts/venafi-kubernetes-agent 42 | helm_chart_version := $(VERSION) 43 | helm_labels_template_name := preflight.labels 44 | 45 | # Allows us to replace the Helm values.yaml's image.repository and image.tag 46 | # with the right values. 47 | define helm_values_mutation_function 48 | $(YQ) \ 49 | '( .image.repository = "$(oci_preflight_image_name)" ) | \ 50 | ( .image.tag = "$(oci_preflight_image_tag)" )' \ 51 | $1 --inplace 52 | endef 53 | 54 | golangci_lint_config := .golangci.yaml 55 | go_header_file := /dev/null 56 | -------------------------------------------------------------------------------- /make/02_mod.mk: -------------------------------------------------------------------------------- 1 | include make/test-unit.mk 2 | 3 | GITHUB_OUTPUT ?= /dev/stderr 4 | .PHONY: release 5 | ## Publish all release artifacts (image + helm chart) 6 | ## @category [shared] Release 7 | release: 8 | $(MAKE) oci-push-preflight 9 | $(MAKE) helm-chart-oci-push 10 | 11 | @echo "RELEASE_OCI_PREFLIGHT_IMAGE=$(oci_preflight_image_name)" >> "$(GITHUB_OUTPUT)" 12 | @echo "RELEASE_OCI_PREFLIGHT_TAG=$(oci_preflight_image_tag)" >> "$(GITHUB_OUTPUT)" 13 | @echo "RELEASE_HELM_CHART_IMAGE=$(helm_chart_image_name)" >> "$(GITHUB_OUTPUT)" 14 | @echo "RELEASE_HELM_CHART_VERSION=$(helm_chart_version)" >> "$(GITHUB_OUTPUT)" 15 | 16 | @echo "Release complete!" 17 | 18 | .PHONY: generate-crds-venconn 19 | ## Pulls the VenafiConnection CRD from the venafi-connection-lib Go module. 20 | ## @category [shared] Generate/ Verify 21 | # 22 | # We aren't using "generate-crds" because "generate-crds" only work for projects 23 | # from which controller-gen can be used to generate the plain CRDs (plain CRDs = 24 | # the non-templated CRDs). In this project, we generate the plain CRDs using `go 25 | # run ./make/connection_crd` instead. 26 | generate-crds-venconn: $(addprefix $(helm_chart_source_dir)/templates/,venafi-connection-crd.yaml venafi-connection-crd.without-validations.yaml) 27 | 28 | $(helm_chart_source_dir)/crd_bases/jetstack.io_venaficonnections.yaml: go.mod | $(NEEDS_GO) 29 | echo "# DO NOT EDIT: Use 'make generate-crds-venconn' to regenerate." >$@ 30 | $(GO) run ./make/connection_crd >>$@ 31 | 32 | $(helm_chart_source_dir)/templates/venafi-connection-crd.without-validations.yaml: $(helm_chart_source_dir)/crd_bases/jetstack.io_venaficonnections.yaml $(helm_chart_source_dir)/crd_bases/crd.header.yaml $(helm_chart_source_dir)/crd_bases/crd.footer.yaml | $(NEEDS_YQ) 33 | cat $(helm_chart_source_dir)/crd_bases/crd.header-without-validations.yaml >$@ 34 | $(YQ) -I2 '{"spec": .spec}' $< | $(YQ) 'del(.. | ."x-kubernetes-validations"?) | del(.metadata.creationTimestamp)' | grep -v "DO NOT EDIT" >>$@ 35 | cat $(helm_chart_source_dir)/crd_bases/crd.footer.yaml >>$@ 36 | 37 | $(helm_chart_source_dir)/templates/venafi-connection-crd.yaml: $(helm_chart_source_dir)/crd_bases/jetstack.io_venaficonnections.yaml $(helm_chart_source_dir)/crd_bases/crd.header.yaml $(helm_chart_source_dir)/crd_bases/crd.footer.yaml | $(NEEDS_YQ) 38 | cat $(helm_chart_source_dir)/crd_bases/crd.header.yaml >$@ 39 | $(YQ) -I2 '{"spec": .spec}' $< | $(YQ) 'del(.metadata.creationTimestamp)' | grep -v "DO NOT EDIT" >>$@ 40 | cat $(helm_chart_source_dir)/crd_bases/crd.footer.yaml >>$@ 41 | 42 | # The generate-crds target doesn't need to be run anymore when running 43 | # "generate". Let's replace it with "generate-crds-venconn". 44 | shared_generate_targets := $(filter-out generate-crds,$(shared_generate_targets)) 45 | shared_generate_targets += generate-crds-venconn 46 | 47 | .PHONY: test-e2e-gke 48 | ## Run a basic E2E test on a GKE cluster 49 | ## Build and install venafi-kubernetes-agent for VenafiConnection based authentication. 50 | ## Wait for it to log a message indicating successful data upload. 51 | ## See `hack/e2e/test.sh` for the full test script. 52 | ## @category Testing 53 | test-e2e-gke: 54 | ./hack/e2e/test.sh 55 | -------------------------------------------------------------------------------- /make/_shared/generate-verify/00_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | shared_generate_targets ?= 16 | shared_generate_targets_dirty ?= 17 | shared_verify_targets ?= 18 | shared_verify_targets_dirty ?= 19 | -------------------------------------------------------------------------------- /make/_shared/generate-verify/02_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | .PHONY: generate 16 | ## Generate all generate targets. 17 | ## @category [shared] Generate/ Verify 18 | generate: $$(shared_generate_targets) 19 | @echo "The following targets cannot be run simultaneously with each other or other generate scripts:" 20 | $(foreach TARGET,$(shared_generate_targets_dirty), $(MAKE) $(TARGET)) 21 | 22 | verify_script := $(dir $(lastword $(MAKEFILE_LIST)))/util/verify.sh 23 | 24 | # Run the supplied make target argument in a temporary workspace and diff the results. 25 | verify-%: FORCE 26 | +$(verify_script) $(MAKE) $* 27 | 28 | verify_generated_targets = $(shared_generate_targets:%=verify-%) 29 | verify_generated_targets_dirty = $(shared_generate_targets_dirty:%=verify-%) 30 | 31 | verify_targets = $(sort $(verify_generated_targets) $(shared_verify_targets)) 32 | verify_targets_dirty = $(sort $(verify_generated_targets_dirty) $(shared_verify_targets_dirty)) 33 | 34 | .PHONY: verify 35 | ## Verify code and generate targets. 36 | ## @category [shared] Generate/ Verify 37 | verify: $$(verify_targets) 38 | @echo "The following targets create temporary files in the current directory, that is why they have to be run last:" 39 | $(foreach TARGET,$(verify_targets_dirty), $(MAKE) $(TARGET)) 40 | -------------------------------------------------------------------------------- /make/_shared/generate-verify/util/verify.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2023 The cert-manager Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Verify that the supplied command does not make any changes to the repository. 18 | # 19 | # This is called from the Makefile to verify that all code generation scripts 20 | # have been run and that their changes have been committed to the repository. 21 | # 22 | # Runs any of the scripts or Make targets in this repository, after making a 23 | # copy of the repository, then reports any changes to the files in the copy. 24 | 25 | # For example: 26 | # 27 | # make verify-helm-chart-update || \ 28 | # make helm-chart-update 29 | # 30 | set -o errexit 31 | set -o nounset 32 | set -o pipefail 33 | 34 | projectdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../../../.." && pwd )" 35 | 36 | cd "${projectdir}" 37 | 38 | # Use short form arguments here to support BSD/macOS. `-d` instructs 39 | # it to make a directory, `-t` provides a prefix to use for the directory name. 40 | tmp="$(mktemp -d /tmp/verify.sh.XXXXXXXX)" 41 | 42 | cleanup() { 43 | rm -rf "${tmp}" 44 | } 45 | trap "cleanup" EXIT SIGINT 46 | 47 | # Why not just "cp" to the tmp dir? 48 | # A dumb "cp" will fail sometimes since _bin can get changed while it's being copied if targets are run in parallel, 49 | # and cp doesn't have some universal "exclude" option to ignore "_bin" 50 | # 51 | # We previously used "rsync" here, but: 52 | # 1. That's another tool we need to depend on 53 | # 2. rsync on macOS 15.4 and newer is actually openrsync, which has different permissions and throws errors when copying git objects 54 | # 55 | # So, we use find to list all files except _bin, and then copy each in turn 56 | find . -maxdepth 1 -not \( -path "./_bin" \) -not \( -path "." \) | xargs -I% cp -af "${projectdir}/%" "${tmp}/" 57 | 58 | pushd "${tmp}" >/dev/null 59 | 60 | "$@" 61 | 62 | popd >/dev/null 63 | 64 | if ! diff \ 65 | --exclude=".git" \ 66 | --exclude="_bin" \ 67 | --new-file --unified --show-c-function --recursive "${projectdir}" "${tmp}" 68 | then 69 | echo 70 | echo "Project '${projectdir}' is out of date." 71 | echo "Please run '${*}' or apply the above diffs" 72 | exit 1 73 | fi 74 | -------------------------------------------------------------------------------- /make/_shared/go/.golangci.override.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | exclusions: 5 | generated: lax 6 | presets: [ comments, common-false-positives, legacy, std-error-handling ] 7 | paths: [ third_party$, builtin$, examples$ ] 8 | warn-unused: true 9 | settings: 10 | staticcheck: 11 | checks: [ "all", "-ST1000", "-ST1001", "-ST1003", "-ST1005", "-ST1012", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1001", "-QF1003", "-QF1008" ] 12 | enable: 13 | - asasalint 14 | - asciicheck 15 | - bidichk 16 | - bodyclose 17 | - canonicalheader 18 | - contextcheck 19 | - copyloopvar 20 | - decorder 21 | - dogsled 22 | - dupword 23 | - durationcheck 24 | - errcheck 25 | - errchkjson 26 | - errname 27 | - exhaustive 28 | - exptostd 29 | - forbidigo 30 | - ginkgolinter 31 | - gocheckcompilerdirectives 32 | - gochecksumtype 33 | - gocritic 34 | - goheader 35 | - goprintffuncname 36 | - gosec 37 | - gosmopolitan 38 | - govet 39 | - grouper 40 | - importas 41 | - ineffassign 42 | - interfacebloat 43 | - intrange 44 | - loggercheck 45 | - makezero 46 | - mirror 47 | - misspell 48 | - musttag 49 | - nakedret 50 | - nilerr 51 | - nilnil 52 | - noctx 53 | - nosprintfhostport 54 | - predeclared 55 | - promlinter 56 | - protogetter 57 | - reassign 58 | - sloglint 59 | - staticcheck 60 | - tagalign 61 | - testableexamples 62 | - unconvert 63 | - unparam 64 | - unused 65 | - usestdlibvars 66 | - usetesting 67 | - wastedassign 68 | formatters: 69 | enable: [ gci, gofmt ] 70 | settings: 71 | gci: 72 | sections: 73 | - standard # Standard section: captures all standard packages. 74 | - default # Default section: contains all imports that could not be matched to another section type. 75 | - prefix({{REPO-NAME}}) # Custom section: groups all imports with the specified Prefix. 76 | - blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled. 77 | - dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled. 78 | exclusions: 79 | generated: lax 80 | paths: [ third_party$, builtin$, examples$ ] 81 | -------------------------------------------------------------------------------- /make/_shared/go/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | A module for various Go static checks. 4 | -------------------------------------------------------------------------------- /make/_shared/go/base/.github/workflows/govulncheck.yaml: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 2 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/go/base/.github/workflows/govulncheck.yaml instead. 3 | 4 | # Run govulncheck at midnight every night on the main branch, 5 | # to alert us to recent vulnerabilities which affect the Go code in this 6 | # project. 7 | name: govulncheck 8 | on: 9 | workflow_dispatch: {} 10 | schedule: 11 | - cron: '0 0 * * *' 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | govulncheck: 18 | runs-on: ubuntu-latest 19 | 20 | if: github.repository_owner == 'cert-manager' 21 | 22 | steps: 23 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 24 | # Adding `fetch-depth: 0` makes sure tags are also fetched. We need 25 | # the tags so `git describe` returns a valid version. 26 | # see https://github.com/actions/checkout/issues/701 for extra info about this option 27 | with: { fetch-depth: 0 } 28 | 29 | - id: go-version 30 | run: | 31 | make print-go-version >> "$GITHUB_OUTPUT" 32 | 33 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 34 | with: 35 | go-version: ${{ steps.go-version.outputs.result }} 36 | 37 | - run: make verify-govulncheck 38 | -------------------------------------------------------------------------------- /make/_shared/helm/01_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef helm_dont_include_crds 16 | include $(dir $(lastword $(MAKEFILE_LIST)))/crds.mk 17 | endif 18 | 19 | include $(dir $(lastword $(MAKEFILE_LIST)))/helm.mk 20 | include $(dir $(lastword $(MAKEFILE_LIST)))/deploy.mk 21 | -------------------------------------------------------------------------------- /make/_shared/helm/crd.template.footer.yaml: -------------------------------------------------------------------------------- 1 | {{- end }} -------------------------------------------------------------------------------- /make/_shared/helm/crd.template.header.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.crds.enabled }} 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: "REPLACE_CRD_NAME" 6 | {{- if .Values.crds.keep }} 7 | annotations: 8 | helm.sh/resource-policy: keep 9 | {{- end }} 10 | labels: 11 | {{- include "REPLACE_LABELS_TEMPLATE" . | nindent 4 }} -------------------------------------------------------------------------------- /make/_shared/helm/crds.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ################ 16 | # Check Inputs # 17 | ################ 18 | 19 | ifndef helm_chart_source_dir 20 | $(error helm_chart_source_dir is not set) 21 | endif 22 | 23 | ifndef helm_labels_template_name 24 | $(error helm_labels_template_name is not set) 25 | endif 26 | 27 | ################ 28 | # Add targets # 29 | ################ 30 | 31 | crd_template_header := $(dir $(lastword $(MAKEFILE_LIST)))/crd.template.header.yaml 32 | crd_template_footer := $(dir $(lastword $(MAKEFILE_LIST)))/crd.template.footer.yaml 33 | 34 | # see https://stackoverflow.com/a/53408233 35 | sed_inplace := sed -i'' 36 | ifeq ($(HOST_OS),darwin) 37 | sed_inplace := sed -i '' 38 | endif 39 | 40 | crds_dir ?= deploy/crds 41 | crds_dir_readme := $(dir $(lastword $(MAKEFILE_LIST)))/crds_dir.README.md 42 | 43 | .PHONY: generate-crds 44 | ## Generate CRD manifests. 45 | ## @category [shared] Generate/ Verify 46 | generate-crds: | $(NEEDS_CONTROLLER-GEN) $(NEEDS_YQ) 47 | $(eval crds_gen_temp := $(bin_dir)/scratch/crds) 48 | $(eval directories := $(shell ls -d */ | grep -v -e 'make' $(shell git check-ignore -- * | sed 's/^/-e /'))) 49 | 50 | rm -rf $(crds_gen_temp) 51 | mkdir -p $(crds_gen_temp) 52 | 53 | $(CONTROLLER-GEN) crd \ 54 | $(directories:%=paths=./%...) \ 55 | output:crd:artifacts:config=$(crds_gen_temp) 56 | 57 | @echo "Updating CRDs with helm templating, writing to $(helm_chart_source_dir)/templates" 58 | 59 | @for i in $$(ls $(crds_gen_temp)); do \ 60 | crd_name=$$($(YQ) eval '.metadata.name' $(crds_gen_temp)/$$i); \ 61 | cat $(crd_template_header) > $(helm_chart_source_dir)/templates/crd-$$i; \ 62 | echo "" >> $(helm_chart_source_dir)/templates/crd-$$i; \ 63 | $(sed_inplace) "s/REPLACE_CRD_NAME/$$crd_name/g" $(helm_chart_source_dir)/templates/crd-$$i; \ 64 | $(sed_inplace) "s/REPLACE_LABELS_TEMPLATE/$(helm_labels_template_name)/g" $(helm_chart_source_dir)/templates/crd-$$i; \ 65 | $(YQ) -I2 '{"spec": .spec}' $(crds_gen_temp)/$$i >> $(helm_chart_source_dir)/templates/crd-$$i; \ 66 | cat $(crd_template_footer) >> $(helm_chart_source_dir)/templates/crd-$$i; \ 67 | done 68 | 69 | @if [ -n "$$(ls $(crds_gen_temp) 2>/dev/null)" ]; then \ 70 | cp $(crds_gen_temp)/* $(crds_dir)/ ; \ 71 | cp $(crds_dir_readme) $(crds_dir)/README.md ; \ 72 | fi 73 | 74 | shared_generate_targets += generate-crds 75 | -------------------------------------------------------------------------------- /make/_shared/helm/crds_dir.README.md: -------------------------------------------------------------------------------- 1 | # CRDs source directory 2 | 3 | > **WARNING**: if you are an end-user, you probably should NOT need to use the 4 | > files in this directory. These files are for **reference, development and testing purposes only**. 5 | 6 | This directory contains 'source code' used to build our CustomResourceDefinition 7 | resources consumed by our officially supported deployment methods (e.g. the Helm chart). 8 | The CRDs in this directory might be incomplete, and should **NOT** be used to provision the operator. -------------------------------------------------------------------------------- /make/_shared/helm/deploy.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef deploy_name 16 | $(error deploy_name is not set) 17 | endif 18 | 19 | ifndef deploy_namespace 20 | $(error deploy_namespace is not set) 21 | endif 22 | 23 | # Install options allows the user configuration of extra flags 24 | INSTALL_OPTIONS ?= 25 | 26 | ########################################## 27 | 28 | .PHONY: install 29 | ## Install controller helm chart on the current active K8S cluster. 30 | ## @category [shared] Deployment 31 | install: $(helm_chart_archive) | $(NEEDS_HELM) 32 | $(HELM) upgrade $(deploy_name) $(helm_chart_archive) \ 33 | --wait \ 34 | --install \ 35 | --create-namespace \ 36 | $(INSTALL_OPTIONS) \ 37 | --namespace $(deploy_namespace) 38 | 39 | .PHONY: uninstall 40 | ## Uninstall controller helm chart from the current active K8S cluster. 41 | ## @category [shared] Deployment 42 | uninstall: | $(NEEDS_HELM) 43 | $(HELM) uninstall $(deploy_name) \ 44 | --wait \ 45 | --namespace $(deploy_namespace) 46 | 47 | .PHONY: template 48 | ## Template the helm chart. 49 | ## @category [shared] Deployment 50 | template: $(helm_chart_archive) | $(NEEDS_HELM) 51 | @$(HELM) template $(deploy_name) $(helm_chart_archive) \ 52 | --create-namespace \ 53 | $(INSTALL_OPTIONS) \ 54 | --namespace $(deploy_namespace) 55 | -------------------------------------------------------------------------------- /make/_shared/help/01_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | help_sh := $(dir $(lastword $(MAKEFILE_LIST)))/help.sh 17 | 18 | .PHONY: help 19 | help: 20 | @MAKEFILE_LIST="$(MAKEFILE_LIST)" \ 21 | MAKE="$(MAKE)" \ 22 | $(help_sh) 23 | -------------------------------------------------------------------------------- /make/_shared/help/help.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2023 The cert-manager Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | ## 1. Build set of extracted line items 22 | 23 | EMPTYLINE_REGEX="^[[:space:]]*$" 24 | DOCBLOCK_REGEX="^##[[:space:]]*(.*)$" 25 | CATEGORY_REGEX="^##[[:space:]]*@category[[:space:]]*(.*)$" 26 | TARGET_REGEX="^(([a-zA-Z0-9\_\/\%\$\(\)]|-)+):.*$" 27 | 28 | EMPTY_ITEM="" 29 | 30 | # shellcheck disable=SC2086 31 | raw_lines=$(cat ${MAKEFILE_LIST} | tr '\t' ' ' | grep -E "($TARGET_REGEX|$DOCBLOCK_REGEX|$EMPTYLINE_REGEX)") 32 | extracted_lines="" 33 | extracted_current="$EMPTY_ITEM" 34 | max_target_length=0 35 | 36 | ## Extract all the commented targets from the Makefile 37 | while read -r line; do 38 | if [[ $line =~ $EMPTYLINE_REGEX ]]; then 39 | # Reset current item. 40 | extracted_current="$EMPTY_ITEM" 41 | elif [[ $line =~ $CATEGORY_REGEX ]]; then 42 | extracted_current=${extracted_current///${BASH_REMATCH[1]}} 43 | elif [[ $line =~ $TARGET_REGEX ]]; then 44 | # only keep the target if there is a comment 45 | if [[ $extracted_current != *""* ]]; then 46 | max_target_length=$(( ${#BASH_REMATCH[1]} > max_target_length ? ${#BASH_REMATCH[1]} : max_target_length )) 47 | extracted_current=${extracted_current///${BASH_REMATCH[1]}} 48 | extracted_lines="$extracted_lines\n$extracted_current" 49 | fi 50 | 51 | extracted_current="$EMPTY_ITEM" 52 | elif [[ $line =~ $DOCBLOCK_REGEX ]]; then 53 | extracted_current=${extracted_current///${BASH_REMATCH[1]}} 54 | fi 55 | done <<< "$raw_lines" 56 | 57 | ## 2. Build mapping for expanding targets 58 | 59 | ASSIGNMENT_REGEX="^(([a-zA-Z0-9\_\/\%\$\(\)]|-)+)[[:space:]]*:=[[:space:]]*(.*)$" 60 | 61 | raw_expansions=$(${MAKE} --dry-run --print-data-base noop | tr '\t' ' ' | grep -E "$ASSIGNMENT_REGEX") 62 | extracted_expansions="" 63 | 64 | while read -r line; do 65 | if [[ $line =~ $ASSIGNMENT_REGEX ]]; then 66 | target=${BASH_REMATCH[1]} 67 | expansion=${BASH_REMATCH[3]// /, } 68 | extracted_expansions="$extracted_expansions\n$target$expansion" 69 | fi 70 | done <<< "$raw_expansions" 71 | 72 | ## 3. Sort and print the extracted line items 73 | 74 | RULE_COLOR="$(TERM=xterm tput setaf 6)" 75 | CATEGORY_COLOR="$(TERM=xterm tput setaf 3)" 76 | CLEAR_STYLE="$(TERM=xterm tput sgr0)" 77 | PURPLE=$(TERM=xterm tput setaf 125) 78 | 79 | extracted_lines=$(echo -e "$extracted_lines" | LC_ALL=C sort -r) 80 | current_category="" 81 | 82 | ## Print the help 83 | echo "Usage: make [target1] [target2] ..." 84 | 85 | IFS=$'\n'; for line in $extracted_lines; do 86 | category=$([[ $line =~ \(.*)\ ]] && echo "${BASH_REMATCH[1]}") 87 | target=$([[ $line =~ \(.*)\ ]] && echo "${BASH_REMATCH[1]}") 88 | comment=$([[ $line =~ \(.*)\ ]] && echo -e "${BASH_REMATCH[1]///\\n}") 89 | 90 | # Print the category header if it's changed 91 | if [[ "$current_category" != "$category" ]]; then 92 | current_category=$category 93 | echo -e "\n${CATEGORY_COLOR}${current_category}${CLEAR_STYLE}" 94 | fi 95 | 96 | # replace any $(...) with the actual value 97 | if [[ $target =~ \$\((.*)\) ]]; then 98 | new_target=$(echo -e "$extracted_expansions" | grep "${BASH_REMATCH[1]}" || true) 99 | if [[ -n "$new_target" ]]; then 100 | target=$([[ $new_target =~ \(.*)\ ]] && echo -e "${BASH_REMATCH[1]}") 101 | fi 102 | fi 103 | 104 | # Print the target and its multiline comment 105 | is_first_line=true 106 | while read -r comment_line; do 107 | if [[ "$is_first_line" == true ]]; then 108 | is_first_line=false 109 | padding=$(( max_target_length - ${#target} )) 110 | printf " %s%${padding}s ${PURPLE}>${CLEAR_STYLE} %s\n" "${RULE_COLOR}${target}${CLEAR_STYLE}" "" "${comment_line}" 111 | else 112 | printf " %${max_target_length}s %s\n" "" "${comment_line}" 113 | fi 114 | done <<< "$comment" 115 | done 116 | -------------------------------------------------------------------------------- /make/_shared/kind/00_kind_image_versions.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This file is auto-generated by the learn_kind_images.sh script in the makefile-modules repo. 16 | # Do not edit manually. 17 | 18 | kind_image_kindversion := v0.27.0 19 | 20 | kind_image_kube_1.29_amd64 := docker.io/kindest/node:v1.29.14@sha256:e7858e6394f5e834802ce573ab340a0584d8314f909cb0717e14b57f2dd97257 21 | kind_image_kube_1.29_arm64 := docker.io/kindest/node:v1.29.14@sha256:6eed9bfd0313cc3574c4613adeb7f53832cb8d9c0ca9ffa8b8221716fd96dc18 22 | kind_image_kube_1.30_amd64 := docker.io/kindest/node:v1.30.10@sha256:e382f9b891474f1c4b0b5cfcf27f8e471f1bdc1f285afe38adeec1bd5b856cfe 23 | kind_image_kube_1.30_arm64 := docker.io/kindest/node:v1.30.10@sha256:ca8e16c04ee9ebaeb9a4dd85abbe188f3893fb39bd658d6d3e639d16cf46e3da 24 | kind_image_kube_1.31_amd64 := docker.io/kindest/node:v1.31.6@sha256:37d52dc19f59394f9347b00547c3ed2d73eb301a60294b9b05fbe56fb6196517 25 | kind_image_kube_1.31_arm64 := docker.io/kindest/node:v1.31.6@sha256:4e6223faa19178922d30e7b62546c5464fdf9bc66a3df64073424a51ab44f2ab 26 | kind_image_kube_1.32_amd64 := docker.io/kindest/node:v1.32.2@sha256:a37b679ad8c1cfa7c64aca1734cc4299dc833258d6c131ed0204c8cd2bd56ff7 27 | kind_image_kube_1.32_arm64 := docker.io/kindest/node:v1.32.2@sha256:4d0e1b60f1da0d1349996a9778f8bace905189af5e05e04618eae0a155dd9f9c 28 | kind_image_kube_1.33_amd64 := docker.io/kindest/node:v1.33.0@sha256:c9ec7bf998c310c5a6c903d66c2e595fb3e2eb53fb626cd53d07a3a5499de412 29 | kind_image_kube_1.33_arm64 := docker.io/kindest/node:v1.33.0@sha256:96ae3b980f87769e0117c2a89ec74fc660b84eedb573432abd2a682af3eccc02 30 | 31 | kind_image_latest_amd64 := $(kind_image_kube_1.33_amd64) 32 | kind_image_latest_arm64 := $(kind_image_kube_1.33_arm64) 33 | -------------------------------------------------------------------------------- /make/_shared/kind/00_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | include $(dir $(lastword $(MAKEFILE_LIST)))/00_kind_image_versions.mk 16 | 17 | images_amd64 ?= 18 | images_arm64 ?= 19 | 20 | # K8S_VERSION can be used to specify a specific 21 | # kubernetes version to use with Kind. 22 | K8S_VERSION ?= 23 | ifeq ($(K8S_VERSION),) 24 | images_amd64 += $(kind_image_latest_amd64) 25 | images_arm64 += $(kind_image_latest_arm64) 26 | else 27 | fatal_if_undefined = $(if $(findstring undefined,$(origin $1)),$(error $1 is not set)) 28 | $(call fatal_if_undefined,kind_image_kube_$(K8S_VERSION)_amd64) 29 | $(call fatal_if_undefined,kind_image_kube_$(K8S_VERSION)_arm64) 30 | 31 | images_amd64 += $(kind_image_kube_$(K8S_VERSION)_amd64) 32 | images_arm64 += $(kind_image_kube_$(K8S_VERSION)_arm64) 33 | endif 34 | -------------------------------------------------------------------------------- /make/_shared/kind/01_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | include $(dir $(lastword $(MAKEFILE_LIST)))/kind.mk 16 | include $(dir $(lastword $(MAKEFILE_LIST)))/kind-image-preload.mk 17 | -------------------------------------------------------------------------------- /make/_shared/kind/kind-image-preload.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef bin_dir 16 | $(error bin_dir is not set) 17 | endif 18 | 19 | ifndef images_amd64 20 | $(error images_amd64 is not set) 21 | endif 22 | 23 | ifndef images_arm64 24 | $(error images_arm64 is not set) 25 | endif 26 | 27 | ########################################## 28 | 29 | images := $(images_$(HOST_ARCH)) 30 | 31 | images_tar_dir := $(bin_dir)/downloaded/containers/$(HOST_ARCH) 32 | images_tars := $(foreach image,$(images),$(images_tar_dir)/$(subst :,+,$(image)).tar) 33 | 34 | # Download the images as tarballs. After downloading the image using 35 | # its digest, we use image-tool to modify the .[0].RepoTags[0] value in 36 | # the manifest.json file to have the correct tag (instead of "i-was-a-digest" 37 | # which is set when the image is pulled using its digest). This tag is used 38 | # to reference the image after it has been imported using docker or kind. Otherwise, 39 | # the image would be imported with the tag "i-was-a-digest" which is not very useful. 40 | # We would have to use digests to reference the image everywhere which might 41 | # not always be possible and does not match the default behavior of eg. our helm charts. 42 | # NOTE: the tag is fully determined based on the input, we fully allow the remote 43 | # tag to point to a different digest. This prevents CI from breaking due to upstream 44 | # changes. However, it also means that we can incorrectly combine digests with tags, 45 | # hence caution is advised. 46 | $(images_tars): $(images_tar_dir)/%.tar: | $(NEEDS_IMAGE-TOOL) $(NEEDS_CRANE) $(NEEDS_GOJQ) 47 | @$(eval full_image=$(subst +,:,$*)) 48 | @$(eval bare_image=$(word 1,$(subst :, ,$(full_image)))) 49 | @$(eval digest=$(word 2,$(subst @, ,$(full_image)))) 50 | @$(eval tag=$(word 2,$(subst :, ,$(word 1,$(subst @, ,$(full_image)))))) 51 | @mkdir -p $(dir $@) 52 | $(CRANE) pull "$(bare_image)@$(digest)" $@ --platform=linux/$(HOST_ARCH) 53 | $(IMAGE-TOOL) tag-docker-tar $@ "$(bare_image):$(tag)" 54 | 55 | # $1 = image 56 | # $2 = image:tag@sha256:digest 57 | define image_variables 58 | $1.TAR := $(images_tar_dir)/$(subst :,+,$2).tar 59 | $1.REPO := $1 60 | $1.TAG := $(word 2,$(subst :, ,$(word 1,$(subst @, ,$2)))) 61 | $1.FULL := $(word 1,$(subst @, ,$2)) 62 | endef 63 | 64 | $(foreach image,$(images),$(eval $(call image_variables,$(word 1,$(subst :, ,$(image))),$(image)))) 65 | 66 | .PHONY: images-preload 67 | ## Preload images. 68 | ## @category [shared] Kind cluster 69 | images-preload: | $(images_tars) 70 | -------------------------------------------------------------------------------- /make/_shared/kind/kind.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ifndef bin_dir 16 | $(error bin_dir is not set) 17 | endif 18 | 19 | ifndef kind_cluster_name 20 | $(error kind_cluster_name is not set) 21 | endif 22 | 23 | ifndef kind_cluster_config 24 | $(error kind_cluster_config is not set) 25 | endif 26 | 27 | ########################################## 28 | 29 | kind_kubeconfig := $(bin_dir)/scratch/kube.config 30 | absolute_kubeconfig := $(CURDIR)/$(kind_kubeconfig) 31 | 32 | $(bin_dir)/scratch/cluster-check: FORCE | $(NEEDS_KIND) $(bin_dir)/scratch 33 | @if ! $(KIND) get clusters -q | grep -q "^$(kind_cluster_name)\$$"; then \ 34 | echo "❌ cluster $(kind_cluster_name) not found. Starting ..."; \ 35 | echo "trigger" > $@; \ 36 | else \ 37 | echo "✅ existing cluster $(kind_cluster_name) found"; \ 38 | fi 39 | $(eval export KUBECONFIG=$(absolute_kubeconfig)) 40 | 41 | kind_post_create_hook ?= 42 | $(kind_kubeconfig): $(kind_cluster_config) $(bin_dir)/scratch/cluster-check | images-preload $(bin_dir)/scratch $(NEEDS_KIND) $(NEEDS_KUBECTL) $(NEEDS_CTR) 43 | @[ -f "$(bin_dir)/scratch/cluster-check" ] && ( \ 44 | $(KIND) delete cluster --name $(kind_cluster_name); \ 45 | $(CTR) load -i $(docker.io/kindest/node.TAR); \ 46 | $(KIND) create cluster \ 47 | --image $(docker.io/kindest/node.FULL) \ 48 | --name $(kind_cluster_name) \ 49 | --config "$<"; \ 50 | $(CTR) exec $(kind_cluster_name)-control-plane find /mounted_images/ -name "*.tar" -exec echo {} \; -exec ctr --namespace=k8s.io images import --all-platforms --no-unpack --digests {} \; ; \ 51 | $(MAKE) --no-print-directory noop $(kind_post_create_hook); \ 52 | $(KUBECTL) config use-context kind-$(kind_cluster_name); \ 53 | ) || true 54 | 55 | $(KIND) get kubeconfig --name $(kind_cluster_name) > $@ 56 | 57 | .PHONY: kind-cluster 58 | kind-cluster: $(kind_kubeconfig) 59 | 60 | .PHONY: kind-cluster-load 61 | ## Create Kind cluster and wait for nodes to be ready 62 | ## Load the kubeconfig into the default location so that 63 | ## it can be easily queried by kubectl. This target is 64 | ## meant to be used directly, NOT as a dependency. 65 | ## Use `kind-cluster` as a dependency instead. 66 | ## @category [shared] Kind cluster 67 | kind-cluster-load: kind-cluster | $(NEEDS_KUBECTL) 68 | mkdir -p ~/.kube 69 | KUBECONFIG=~/.kube/config:$(kind_kubeconfig) $(KUBECTL) config view --flatten > ~/.kube/config 70 | $(KUBECTL) config use-context kind-$(kind_cluster_name) 71 | 72 | .PHONY: kind-cluster-clean 73 | ## Delete the Kind cluster 74 | ## @category [shared] Kind cluster 75 | kind-cluster-clean: $(NEEDS_KIND) 76 | $(KIND) delete cluster --name $(kind_cluster_name) 77 | rm -rf $(kind_kubeconfig) 78 | $(MAKE) --no-print-directory noop $(kind_post_create_hook) 79 | 80 | .PHONY: kind-logs 81 | ## Get the Kind cluster 82 | ## @category [shared] Kind cluster 83 | kind-logs: | kind-cluster $(NEEDS_KIND) $(ARTIFACTS) 84 | rm -rf $(ARTIFACTS)/e2e-logs 85 | mkdir -p $(ARTIFACTS)/e2e-logs 86 | $(KIND) export logs $(ARTIFACTS)/e2e-logs --name=$(kind_cluster_name) 87 | -------------------------------------------------------------------------------- /make/_shared/klone/01_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | .PHONY: generate-klone 16 | ## Generate klone shared Makefiles 17 | ## @category [shared] Generate/ Verify 18 | generate-klone: | $(NEEDS_KLONE) 19 | $(KLONE) sync 20 | 21 | shared_generate_targets += generate-klone 22 | 23 | .PHONY: upgrade-klone 24 | ## Upgrade klone Makefile modules to latest version 25 | ## @category [shared] Self-upgrade 26 | upgrade-klone: | $(NEEDS_KLONE) 27 | $(KLONE) upgrade 28 | -------------------------------------------------------------------------------- /make/_shared/licenses/00_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Define default config for generating licenses 16 | license_ignore ?= 17 | -------------------------------------------------------------------------------- /make/_shared/licenses/01_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ###################### Generate LICENSES files ###################### 16 | 17 | # Create a go.work file so that go-licenses can discover the LICENSE file of the 18 | # other modules in the repo. 19 | # 20 | # Without this, go-licenses *guesses* the wrong LICENSE for local dependencies and 21 | # links to the wrong versions of LICENSES for transitive dependencies. 22 | licenses_go_work := $(bin_dir)/scratch/LICENSES.go.work 23 | $(licenses_go_work): $(bin_dir)/scratch 24 | GOWORK=$(abspath $@) \ 25 | $(MAKE) go-workspace 26 | 27 | ## Generate licenses for the golang dependencies 28 | ## @category [shared] Generate/Verify 29 | generate-go-licenses: # 30 | shared_generate_targets += generate-go-licenses 31 | 32 | define licenses_target 33 | $1/LICENSES: $1/go.mod $(licenses_go_work) | $(NEEDS_GO-LICENSES) 34 | cd $$(dir $$@) && \ 35 | GOWORK=$(abspath $(licenses_go_work)) \ 36 | GOOS=linux GOARCH=amd64 \ 37 | $(GO-LICENSES) report --ignore "$$(license_ignore)" ./... > LICENSES 38 | 39 | generate-go-licenses: $1/LICENSES 40 | # The /LICENSE targets make sure these files exist. 41 | # Otherwise, make will error. 42 | generate-go-licenses: $1/LICENSE 43 | endef 44 | 45 | # Calculate all the go.mod directories, build targets may share go.mod dirs so 46 | # we use $(sort) to de-duplicate. 47 | go_mod_dirs := $(foreach build_name,$(build_names),$(go_$(build_name)_mod_dir)) 48 | ifneq ("$(wildcard go.mod)","") 49 | go_mod_dirs += . 50 | endif 51 | go_mod_dirs := $(sort $(go_mod_dirs)) 52 | $(foreach go_mod_dir,$(go_mod_dirs),$(eval $(call licenses_target,$(go_mod_dir)))) 53 | 54 | ###################### Include LICENSES in OCI image ###################### 55 | 56 | define license_layer 57 | license_layer_path_$1 := $$(abspath $(bin_dir)/scratch/licenses-$1) 58 | 59 | # Target to generate image layer containing license information 60 | .PHONY: oci-license-layer-$1 61 | oci-license-layer-$1: | $(bin_dir)/scratch $(NEEDS_GO-LICENSES) 62 | rm -rf $$(license_layer_path_$1) 63 | mkdir -p $$(license_layer_path_$1)/licenses 64 | cp $$(go_$1_mod_dir)/LICENSE $$(license_layer_path_$1)/licenses/LICENSE 65 | cp $$(go_$1_mod_dir)/LICENSES $$(license_layer_path_$1)/licenses/LICENSES 66 | 67 | oci-build-$1: oci-license-layer-$1 68 | oci_$1_additional_layers += $$(license_layer_path_$1) 69 | endef 70 | 71 | $(foreach build_name,$(build_names),$(eval $(call license_layer,$(build_name)))) 72 | -------------------------------------------------------------------------------- /make/_shared/oci-build/01_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | $(bin_dir)/scratch/image: 16 | @mkdir -p $@ 17 | 18 | define ko_config_target 19 | .PHONY: $(ko_config_path_$1:$(CURDIR)/%=%) 20 | $(ko_config_path_$1:$(CURDIR)/%=%): | $(NEEDS_YQ) $(bin_dir)/scratch/image 21 | echo '{}' | \ 22 | $(YQ) '.defaultBaseImage = "$(oci_$1_base_image)"' | \ 23 | $(YQ) '.builds[0].id = "$1"' | \ 24 | $(YQ) '.builds[0].dir = "$(go_$1_mod_dir)"' | \ 25 | $(YQ) '.builds[0].main = "$(go_$1_main_dir)"' | \ 26 | $(YQ) '.builds[0].env[0] = "CGO_ENABLED=$(go_$1_cgo_enabled)"' | \ 27 | $(YQ) '.builds[0].env[1] = "GOEXPERIMENT=$(go_$1_goexperiment)"' | \ 28 | $(YQ) '.builds[0].ldflags[0] = "-s"' | \ 29 | $(YQ) '.builds[0].ldflags[1] = "-w"' | \ 30 | $(YQ) '.builds[0].ldflags[2] = "{{.Env.LDFLAGS}}"' | \ 31 | $(YQ) '.builds[0].flags[0] = "$(go_$1_flags)"' | \ 32 | $(YQ) '.builds[0].linux_capabilities = "$(oci_$1_linux_capabilities)"' \ 33 | > $(CURDIR)/$(oci_layout_path_$1).ko_config.yaml 34 | 35 | ko-config-$1: $(ko_config_path_$1:$(CURDIR)/%=%) 36 | endef 37 | 38 | .PHONY: $(ko_config_targets) 39 | $(foreach build_name,$(build_names),$(eval $(call ko_config_target,$(build_name)))) 40 | 41 | .PHONY: $(oci_build_targets) 42 | ## Build the OCI image. 43 | ## @category [shared] Build 44 | $(oci_build_targets): oci-build-%: ko-config-% | $(NEEDS_KO) $(NEEDS_GO) $(NEEDS_YQ) $(NEEDS_IMAGE-TOOL) $(bin_dir)/scratch/image 45 | rm -rf $(CURDIR)/$(oci_layout_path_$*) 46 | GOWORK=off \ 47 | KO_DOCKER_REPO=$(oci_$*_image_name_development) \ 48 | KOCACHE=$(CURDIR)/$(bin_dir)/scratch/image/ko_cache \ 49 | KO_CONFIG_PATH=$(ko_config_path_$*) \ 50 | SOURCE_DATE_EPOCH=$(GITEPOCH) \ 51 | KO_GO_PATH=$(GO) \ 52 | LDFLAGS="$(go_$*_ldflags)" \ 53 | $(KO) build $(go_$*_mod_dir)/$(go_$*_main_dir) \ 54 | --platform=$(oci_platforms) \ 55 | $(oci_$*_build_args) \ 56 | --oci-layout-path=$(oci_layout_path_$*) \ 57 | --sbom-dir=$(CURDIR)/$(oci_layout_path_$*).sbom \ 58 | --sbom=spdx \ 59 | --push=false \ 60 | --bare 61 | 62 | $(IMAGE-TOOL) append-layers \ 63 | $(CURDIR)/$(oci_layout_path_$*) \ 64 | $(oci_$*_additional_layers) 65 | 66 | $(IMAGE-TOOL) list-digests \ 67 | $(CURDIR)/$(oci_layout_path_$*) \ 68 | > $(oci_digest_path_$*) 69 | 70 | # Only include the oci-load target if kind is provided by the kind makefile-module 71 | ifdef kind_cluster_name 72 | .PHONY: $(oci_load_targets) 73 | ## Build OCI image for the local architecture and load 74 | ## it into the $(kind_cluster_name) kind cluster. 75 | ## @category [shared] Build 76 | $(oci_load_targets): oci-load-%: docker-tarball-% | kind-cluster $(NEEDS_KIND) 77 | $(KIND) load image-archive --name $(kind_cluster_name) $(docker_tarball_path_$*) 78 | endif 79 | 80 | ## Build Docker tarball image for the local architecture 81 | ## @category [shared] Build 82 | .PHONY: $(docker_tarball_targets) 83 | $(docker_tarball_targets): oci_platforms := "linux/$(HOST_ARCH)" 84 | $(docker_tarball_targets): docker-tarball-%: oci-build-% | $(NEEDS_GO) $(NEEDS_IMAGE-TOOL) 85 | $(IMAGE-TOOL) convert-to-docker-tar $(CURDIR)/$(oci_layout_path_$*) $(docker_tarball_path_$*) $(oci_$*_image_name_development):$(oci_$*_image_tag) 86 | -------------------------------------------------------------------------------- /make/_shared/oci-publish/00_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Push names is equivalent to build_names, additional names can be added for 16 | # pushing images that are not build with the oci-build module 17 | push_names ?= 18 | push_names += $(build_names) 19 | 20 | # Sometimes we need to push to one registry, but pull from another. This allows 21 | # that. 22 | # 23 | # The lines should be in the format a=b 24 | # 25 | # The value on the left is the domain you include in your oci__image_name 26 | # variable, the one on the right is the domain that is actually pushed to. 27 | # 28 | # For example, if we set up a vanity domain for the current quay: 29 | # 30 | # oci_controller_image_name = registry.cert-manager.io/cert-manager-controller` 31 | # image_registry_rewrite += registry.cert-manager.io=quay.io/jetstack 32 | # 33 | # This would push to quay.io/jetstack/cert-manager-controller. 34 | # 35 | # The general idea is oci__image_name contains the final image name, after replication, after vanity domains etc. 36 | 37 | image_registry_rewrite ?= 38 | 39 | # Utilities for extracting the key and value from a foo=bar style line 40 | kv_key = $(word 1,$(subst =, ,$1)) 41 | kv_value = $(word 2,$(subst =, ,$1)) 42 | 43 | # Apply the image_registry_rewrite rules, if no rules match an image then the 44 | # image name is not changed. Any rules that match will be applied. 45 | # 46 | # For example, if there was a rule vanity-domain.com=real-registry.com/foo 47 | # then any references to vanity-domain.com/image would be rewritten to 48 | # real-registry.com/foo/image 49 | image_registry_rewrite_rules_for_image = $(strip $(sort $(foreach rule,$(image_registry_rewrite),$(if $(findstring $(call kv_key,$(rule)),$1),$(rule))))) 50 | apply_image_registry_rewrite_rules_to_image = $(if $(call image_registry_rewrite_rules_for_image,$1),\ 51 | $(foreach rule,$(call image_registry_rewrite_rules_for_image,$1),$(subst $(call kv_key,$(rule)),$(call kv_value,$(rule)),$1)),\ 52 | $1) 53 | apply_image_registry_rewrite_rules = $(foreach image_name,$1,$(call apply_image_registry_rewrite_rules_to_image,$(image_name))) 54 | 55 | # This is a helper function to return the image names for a given build_name. 56 | # It will apply all rewrite rules to the image names 57 | oci_image_names_for = $(call apply_image_registry_rewrite_rules,$(oci_$1_image_name)) 58 | oci_image_tag_for = $(oci_$1_image_tag) -------------------------------------------------------------------------------- /make/_shared/oci-publish/image-exists.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2022 The cert-manager Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | # This script checks if a given image exists in the upstream registry, and if it 22 | # does, whether it contains all the expected architectures. 23 | 24 | crane=${CRANE:-} 25 | 26 | FULL_IMAGE=${1:-} 27 | 28 | function print_usage() { 29 | echo "usage: $0 [commands...]" 30 | } 31 | 32 | if [[ -z $FULL_IMAGE ]]; then 33 | print_usage 34 | echo "Missing full-image" 35 | exit 1 36 | fi 37 | 38 | if [[ -z $crane ]]; then 39 | echo "CRANE environment variable must be set to the path of the crane binary" 40 | exit 1 41 | fi 42 | 43 | shift 1 44 | 45 | manifest=$(mktemp) 46 | trap 'rm -f "$manifest"' EXIT SIGINT 47 | 48 | manifest_error=$(mktemp) 49 | trap 'rm -f "$manifest_error"' EXIT SIGINT 50 | 51 | echo "+++ searching for $FULL_IMAGE in upstream registry" 52 | 53 | set +o errexit 54 | $crane manifest "$FULL_IMAGE" > "$manifest" 2> "$manifest_error" 55 | exit_code=$? 56 | set -o errexit 57 | 58 | manifest_error_data=$(cat "$manifest_error") 59 | if [[ $exit_code -eq 0 ]]; then 60 | echo "+++ upstream registry appears to contain $FULL_IMAGE, exiting" 61 | exit 0 62 | 63 | elif [[ "$manifest_error_data" == *"MANIFEST_UNKNOWN"* ]]; then 64 | echo "+++ upstream registry does not contain $FULL_IMAGE, will build and push" 65 | # fall through to run the commands passed to this script 66 | 67 | else 68 | echo "FATAL: upstream registry returned an unexpected error: $manifest_error_data, exiting" 69 | exit 1 70 | fi 71 | -------------------------------------------------------------------------------- /make/_shared/repository-base/01_mod.mk: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | base_dir := $(dir $(lastword $(MAKEFILE_LIST)))/base/ 16 | base_dependabot_dir := $(dir $(lastword $(MAKEFILE_LIST)))/base-dependabot/ 17 | 18 | ifdef repository_base_no_dependabot 19 | .PHONY: generate-base 20 | ## Generate base files in the repository 21 | ## @category [shared] Generate/ Verify 22 | generate-base: 23 | cp -r $(base_dir)/. ./ 24 | else 25 | .PHONY: generate-base 26 | ## Generate base files in the repository 27 | ## @category [shared] Generate/ Verify 28 | generate-base: 29 | cp -r $(base_dir)/. ./ 30 | cp -r $(base_dependabot_dir)/. ./ 31 | endif 32 | 33 | shared_generate_targets += generate-base 34 | -------------------------------------------------------------------------------- /make/_shared/repository-base/base-dependabot/.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 2 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base-dependabot/.github/dependabot.yaml instead. 3 | 4 | # Update Go dependencies and GitHub Actions dependencies daily. 5 | version: 2 6 | updates: 7 | - package-ecosystem: gomod 8 | directory: / 9 | schedule: 10 | interval: daily 11 | groups: 12 | all: 13 | patterns: ["*"] 14 | - package-ecosystem: github-actions 15 | directory: / 16 | schedule: 17 | interval: daily 18 | groups: 19 | all: 20 | patterns: ["*"] 21 | -------------------------------------------------------------------------------- /make/_shared/repository-base/base/.github/workflows/make-self-upgrade.yaml: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 2 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/.github/workflows/make-self-upgrade.yaml instead. 3 | 4 | name: make-self-upgrade 5 | concurrency: make-self-upgrade 6 | on: 7 | workflow_dispatch: {} 8 | schedule: 9 | - cron: '0 0 * * *' 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | self_upgrade: 16 | runs-on: ubuntu-latest 17 | 18 | if: github.repository_owner == 'cert-manager' 19 | 20 | permissions: 21 | contents: write 22 | pull-requests: write 23 | 24 | env: 25 | SOURCE_BRANCH: "${{ github.ref_name }}" 26 | SELF_UPGRADE_BRANCH: "self-upgrade-${{ github.ref_name }}" 27 | 28 | steps: 29 | - name: Fail if branch is not head of branch. 30 | if: ${{ !startsWith(github.ref, 'refs/heads/') && env.SOURCE_BRANCH != '' && env.SELF_UPGRADE_BRANCH != '' }} 31 | run: | 32 | echo "This workflow should not be run on a non-branch-head." 33 | exit 1 34 | 35 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 36 | # Adding `fetch-depth: 0` makes sure tags are also fetched. We need 37 | # the tags so `git describe` returns a valid version. 38 | # see https://github.com/actions/checkout/issues/701 for extra info about this option 39 | with: { fetch-depth: 0 } 40 | 41 | - id: go-version 42 | run: | 43 | make print-go-version >> "$GITHUB_OUTPUT" 44 | 45 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 46 | with: 47 | go-version: ${{ steps.go-version.outputs.result }} 48 | 49 | - run: | 50 | git checkout -B "$SELF_UPGRADE_BRANCH" 51 | 52 | - run: | 53 | make -j upgrade-klone 54 | make -j generate 55 | 56 | - id: is-up-to-date 57 | shell: bash 58 | run: | 59 | git_status=$(git status -s) 60 | is_up_to_date="true" 61 | if [ -n "$git_status" ]; then 62 | is_up_to_date="false" 63 | echo "The following changes will be committed:" 64 | echo "$git_status" 65 | fi 66 | echo "result=$is_up_to_date" >> "$GITHUB_OUTPUT" 67 | 68 | - if: ${{ steps.is-up-to-date.outputs.result != 'true' }} 69 | run: | 70 | git config --global user.name "cert-manager-bot" 71 | git config --global user.email "cert-manager-bot@users.noreply.github.com" 72 | git add -A && git commit -m "BOT: run 'make upgrade-klone' and 'make generate'" --signoff 73 | git push -f origin "$SELF_UPGRADE_BRANCH" 74 | 75 | - if: ${{ steps.is-up-to-date.outputs.result != 'true' }} 76 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 77 | with: 78 | script: | 79 | const { repo, owner } = context.repo; 80 | const pulls = await github.rest.pulls.list({ 81 | owner: owner, 82 | repo: repo, 83 | head: owner + ':' + process.env.SELF_UPGRADE_BRANCH, 84 | base: process.env.SOURCE_BRANCH, 85 | state: 'open', 86 | }); 87 | 88 | if (pulls.data.length < 1) { 89 | const result = await github.rest.pulls.create({ 90 | title: '[CI] Merge ' + process.env.SELF_UPGRADE_BRANCH + ' into ' + process.env.SOURCE_BRANCH, 91 | owner: owner, 92 | repo: repo, 93 | head: process.env.SELF_UPGRADE_BRANCH, 94 | base: process.env.SOURCE_BRANCH, 95 | body: [ 96 | 'This PR is auto-generated to bump the Makefile modules.', 97 | ].join('\n'), 98 | }); 99 | await github.rest.issues.addLabels({ 100 | owner, 101 | repo, 102 | issue_number: result.data.number, 103 | labels: ['skip-review'] 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /make/_shared/repository-base/base/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The cert-manager Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 16 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/Makefile instead. 17 | 18 | # NOTE FOR DEVELOPERS: "How do the Makefiles work and how can I extend them?" 19 | # 20 | # Shared Makefile logic lives in the make/_shared/ directory. The source of truth for these files 21 | # lies outside of this repository, eg. in the cert-manager/makefile-modules repository. 22 | # 23 | # Logic specific to this repository must be defined in the make/00_mod.mk and make/02_mod.mk files: 24 | # - The make/00_mod.mk file is included first and contains variable definitions needed by 25 | # the shared Makefile logic. 26 | # - The make/02_mod.mk file is included later, it can make use of most of the shared targets 27 | # defined in the make/_shared/ directory (all targets defined in 00_mod.mk and 01_mod.mk). 28 | # This file should be used to define targets specific to this repository. 29 | 30 | ################################## 31 | 32 | # Some modules build their dependencies from variables, we want these to be 33 | # evaluated at the last possible moment. For this we use second expansion to 34 | # re-evaluate the generate and verify targets a second time. 35 | # 36 | # See https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html 37 | .SECONDEXPANSION: 38 | 39 | # For details on some of these "prelude" settings, see: 40 | # https://clarkgrubb.com/makefile-style-guide 41 | MAKEFLAGS += --warn-undefined-variables --no-builtin-rules 42 | SHELL := /usr/bin/env bash 43 | .SHELLFLAGS := -uo pipefail -c 44 | .DEFAULT_GOAL := help 45 | .DELETE_ON_ERROR: 46 | .SUFFIXES: 47 | FORCE: 48 | 49 | noop: # do nothing 50 | 51 | # Set empty value for MAKECMDGOALS to prevent the "warning: undefined variable 'MAKECMDGOALS'" 52 | # warning from happening when running make without arguments 53 | MAKECMDGOALS ?= 54 | 55 | ################################## 56 | # Host OS and architecture setup # 57 | ################################## 58 | 59 | # The reason we don't use "go env GOOS" or "go env GOARCH" is that the "go" 60 | # binary may not be available in the PATH yet when the Makefiles are 61 | # evaluated. HOST_OS and HOST_ARCH only support Linux, *BSD and macOS (M1 62 | # and Intel). 63 | host_os := $(shell uname -s | tr A-Z a-z) 64 | host_arch := $(shell uname -m) 65 | HOST_OS ?= $(host_os) 66 | HOST_ARCH ?= $(host_arch) 67 | 68 | ifeq (x86_64, $(HOST_ARCH)) 69 | HOST_ARCH = amd64 70 | else ifeq (aarch64, $(HOST_ARCH)) 71 | # linux reports the arm64 arch as aarch64 72 | HOST_ARCH = arm64 73 | endif 74 | 75 | ################################## 76 | # Git and versioning information # 77 | ################################## 78 | 79 | git_version := $(shell git describe --tags --always --match='v*' --abbrev=14 --dirty) 80 | VERSION ?= $(git_version) 81 | IS_PRERELEASE := $(shell git describe --tags --always --match='v*' --abbrev=0 | grep -q '-' && echo true || echo false) 82 | GITCOMMIT := $(shell git rev-parse HEAD) 83 | GITEPOCH := $(shell git show -s --format=%ct HEAD) 84 | 85 | ################################## 86 | # Global variables and dirs # 87 | ################################## 88 | 89 | bin_dir := _bin 90 | 91 | # The ARTIFACTS environment variable is set by the CI system to a directory 92 | # where artifacts should be placed. These artifacts are then uploaded to a 93 | # storage bucket by the CI system (https://docs.prow.k8s.io/docs/components/pod-utilities/). 94 | # An example of such an artifact is a jUnit XML file containing test results. 95 | # If the ARTIFACTS environment variable is not set, we default to a local 96 | # directory in the _bin directory. 97 | ARTIFACTS ?= $(bin_dir)/artifacts 98 | 99 | $(bin_dir) $(ARTIFACTS) $(bin_dir)/scratch: 100 | mkdir -p $@ 101 | 102 | .PHONY: clean 103 | ## Clean all temporary files 104 | ## @category [shared] Tools 105 | clean: 106 | rm -rf $(bin_dir) 107 | 108 | ################################## 109 | # Include all the Makefiles # 110 | ################################## 111 | 112 | -include make/00_mod.mk 113 | -include make/_shared/*/00_mod.mk 114 | -include make/_shared/*/01_mod.mk 115 | -include make/02_mod.mk 116 | -include make/_shared/*/02_mod.mk 117 | -------------------------------------------------------------------------------- /make/_shared/repository-base/base/OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. 2 | # Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/OWNERS_ALIASES instead. 3 | 4 | aliases: 5 | cm-maintainers: 6 | - munnerz 7 | - joshvanl 8 | - wallrj 9 | - jakexks 10 | - maelvls 11 | - sgtcodfish 12 | - inteon 13 | - thatsmrtalbot 14 | - erikgb 15 | -------------------------------------------------------------------------------- /make/_shared/tools/util/checkhash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2023 The cert-manager Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 22 | 23 | # This script takes the hash of its first argument and verifies it against the 24 | # hex hash given in its second argument 25 | 26 | function usage_and_exit() { 27 | echo "usage: $0 " 28 | echo "or: LEARN_FILE= $0 " 29 | exit 1 30 | } 31 | 32 | HASH_TARGET=${1:-} 33 | EXPECTED_HASH=${2:-} 34 | 35 | if [[ -z $HASH_TARGET ]]; then 36 | usage_and_exit 37 | fi 38 | 39 | if [[ -z $EXPECTED_HASH ]]; then 40 | usage_and_exit 41 | fi 42 | 43 | SHASUM=$("${SCRIPT_DIR}/hash.sh" "$HASH_TARGET") 44 | 45 | if [[ "$SHASUM" == "$EXPECTED_HASH" ]]; then 46 | exit 0 47 | fi 48 | 49 | # When running 'make learn-sha-tools', we don't want this script to fail. 50 | # Instead we log what sha values are wrong, so the make.mk file can be updated. 51 | 52 | if [ "${LEARN_FILE:-}" != "" ]; then 53 | echo "s/$EXPECTED_HASH/$SHASUM/g" >> "${LEARN_FILE:-}" 54 | exit 0 55 | fi 56 | 57 | echo "invalid checksum for \"$HASH_TARGET\": wanted \"$EXPECTED_HASH\" but got \"$SHASUM\"" 58 | exit 1 59 | -------------------------------------------------------------------------------- /make/_shared/tools/util/hash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2023 The cert-manager Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | # This script is a wrapper for outputting purely the sha256 hash of the input file, 22 | # ideally in a portable way. 23 | 24 | case "$(uname -s)" in 25 | Darwin*) shasum -a 256 "$1";; 26 | *) sha256sum "$1" 27 | esac | cut -d" " -f1 -------------------------------------------------------------------------------- /make/_shared/tools/util/lock.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2023 The cert-manager Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit 18 | set -o nounset 19 | set -o pipefail 20 | 21 | # This script is used to lock a file while it is being downloaded. It prevents 22 | # multiple processes from downloading the same file at the same time or from reading 23 | # a half-downloaded file. 24 | # We need this solution because we have recursive $(MAKE) calls in our makefile 25 | # which each will try to download a set of tools. To prevent them from all downloading 26 | # the same files, we re-use the same downloads folder for all $(MAKE) invocations and 27 | # use this script to deduplicate the download processes. 28 | 29 | finalfile="$1" 30 | lockfile="$finalfile.lock" 31 | 32 | # On macOS, flock is not installed, we just skip locking in that case, 33 | # this means that running verify in parallel without downloading all 34 | # tools first will not work. 35 | flock_installed=$(command -v flock >/dev/null && echo "yes" || echo "no") 36 | 37 | if [[ "$flock_installed" == "yes" ]]; then 38 | mkdir -p "$(dirname "$lockfile")" 39 | touch "$lockfile" 40 | exec {FD}<>"$lockfile" 41 | 42 | # wait for the file to be unlocked 43 | if ! flock -x $FD; then 44 | echo "Failed to obtain a lock for $lockfile" 45 | exit 1 46 | fi 47 | fi 48 | 49 | # now that we have the lock, check if file is already there 50 | if [[ -e "$finalfile" ]]; then 51 | exit 0 52 | fi 53 | 54 | # use a temporary file to prevent Make from thinking the file is ready 55 | # while in reality is is only a partial download 56 | # shellcheck disable=SC2034 57 | outfile="$finalfile.tmp" 58 | 59 | finish() { 60 | rv=$? 61 | if [[ $rv -eq 0 ]]; then 62 | mv "$outfile" "$finalfile" 63 | echo "[info]: downloaded $finalfile" 64 | else 65 | rm -rf "$outfile" || true 66 | rm -rf "$finalfile" || true 67 | fi 68 | rm -rf "$lockfile" || true 69 | } 70 | trap finish EXIT SIGINT 71 | -------------------------------------------------------------------------------- /make/connection_crd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | crd "github.com/jetstack/venafi-connection-lib/config/crd/bases" 7 | ) 8 | 9 | // With this tool, we no longer have to use something like `helm template` to 10 | // pull the CRD manifest from the venafi-connection-lib project. 11 | func main() { 12 | fmt.Print(string(crd.VenafiConnectionCrd)) 13 | } 14 | -------------------------------------------------------------------------------- /make/test-unit.mk: -------------------------------------------------------------------------------- 1 | .PHONY: test-unit 2 | ## Unit tests 3 | ## @category Testing 4 | test-unit: | $(NEEDS_GO) $(NEEDS_GOTESTSUM) $(ARTIFACTS) $(NEEDS_ETCD) $(NEEDS_KUBE-APISERVER) 5 | KUBEBUILDER_ASSETS=$(CURDIR)/$(bin_dir)/tools \ 6 | $(GOTESTSUM) \ 7 | --junitfile=$(ARTIFACTS)/junit-go-e2e.xml \ 8 | -- \ 9 | -coverprofile=$(ARTIFACTS)/filtered.cov \ 10 | ./api/... ./pkg/... \ 11 | -- \ 12 | -ldflags $(go_preflight_ldflags) 13 | 14 | $(GO) tool cover -html=$(ARTIFACTS)/filtered.cov -o=$(ARTIFACTS)/filtered.html 15 | -------------------------------------------------------------------------------- /pkg/agent/dummy_data_gatherer.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/jetstack/preflight/pkg/datagatherer" 8 | ) 9 | 10 | type dummyConfig struct { 11 | AlwaysFail bool `yaml:"always-fail"` 12 | FailedAttempts int `yaml:"failed-attempts"` 13 | wantOnCreationErr bool 14 | } 15 | 16 | func (c *dummyConfig) NewDataGatherer(ctx context.Context) (datagatherer.DataGatherer, error) { 17 | if c.wantOnCreationErr { 18 | return nil, fmt.Errorf("an error") 19 | } 20 | return &dummyDataGatherer{ 21 | AlwaysFail: c.AlwaysFail, 22 | FailedAttempts: c.FailedAttempts, 23 | }, nil 24 | } 25 | 26 | type dummyDataGatherer struct { 27 | AlwaysFail bool 28 | attemptNumber int 29 | FailedAttempts int 30 | } 31 | 32 | func (g *dummyDataGatherer) Run(stopCh <-chan struct{}) error { 33 | // no async functionality, see Fetch 34 | return nil 35 | } 36 | 37 | func (g *dummyDataGatherer) WaitForCacheSync(stopCh <-chan struct{}) error { 38 | // no async functionality, see Fetch 39 | return nil 40 | } 41 | 42 | func (g *dummyDataGatherer) Delete() error { 43 | // no async functionality, see Fetch 44 | return nil 45 | } 46 | 47 | func (c *dummyDataGatherer) Fetch() (interface{}, int, error) { 48 | var err error 49 | if c.attemptNumber < c.FailedAttempts { 50 | err = fmt.Errorf("First %d attempts will fail", c.FailedAttempts) 51 | } 52 | if c.AlwaysFail { 53 | err = fmt.Errorf("This data gatherer will always fail") 54 | } 55 | c.attemptNumber++ 56 | return nil, -1, err 57 | } 58 | -------------------------------------------------------------------------------- /pkg/agent/metrics.go: -------------------------------------------------------------------------------- 1 | package agent 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | ) 6 | 7 | var ( 8 | metricPayloadSize = prometheus.NewGaugeVec( 9 | prometheus.GaugeOpts{ 10 | Namespace: "jscp", 11 | Subsystem: "agent", 12 | Name: "data_readings_upload_size", 13 | Help: "Data readings upload size (in bytes) sent by the jscp in-cluster agent.", 14 | }, []string{"organization", "cluster"}) 15 | ) 16 | -------------------------------------------------------------------------------- /pkg/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/jetstack/preflight/api" 11 | ) 12 | 13 | type ( 14 | // Options is the struct describing additional information pertinent to an agent that isn't a data reading 15 | // These fields will then be uploaded together with data readings. 16 | Options struct { 17 | // Only used with Jetstack Secure. 18 | OrgID string 19 | 20 | // Only used with Jetstack Secure. 21 | ClusterID string 22 | 23 | // Only used with Venafi Cloud. The convention is to use the agent 24 | // config's `cluster_id` as ClusterName. 25 | ClusterName string 26 | 27 | // Only used with Venafi Cloud. 28 | ClusterDescription string 29 | } 30 | 31 | // The Client interface describes types that perform requests against the Jetstack Secure backend. 32 | Client interface { 33 | PostDataReadings(ctx context.Context, orgID, clusterID string, readings []*api.DataReading) error 34 | PostDataReadingsWithOptions(ctx context.Context, readings []*api.DataReading, options Options) error 35 | Post(ctx context.Context, path string, body io.Reader) (*http.Response, error) 36 | } 37 | 38 | // The Credentials interface describes methods for credential types to implement for verification. 39 | Credentials interface { 40 | IsClientSet() (ok bool, why string) 41 | Validate() error 42 | } 43 | ) 44 | 45 | func fullURL(baseURL, path string) string { 46 | base := baseURL 47 | for strings.HasSuffix(base, "/") { 48 | base = strings.TrimSuffix(base, "/") 49 | } 50 | for strings.HasPrefix(path, "/") { 51 | path = strings.TrimPrefix(path, "/") 52 | } 53 | return fmt.Sprintf("%s/%s", base, path) 54 | } 55 | -------------------------------------------------------------------------------- /pkg/client/client_api_token.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "path/filepath" 11 | "time" 12 | 13 | "k8s.io/client-go/transport" 14 | 15 | "github.com/jetstack/preflight/api" 16 | "github.com/jetstack/preflight/pkg/version" 17 | ) 18 | 19 | type ( 20 | // The APITokenClient type is a Client implementation used to upload data readings to the Jetstack Secure platform 21 | // using API tokens as its authentication method. 22 | APITokenClient struct { 23 | apiToken string 24 | baseURL string 25 | agentMetadata *api.AgentMetadata 26 | client *http.Client 27 | } 28 | ) 29 | 30 | // NewAPITokenClient returns a new instance of the APITokenClient type that will perform HTTP requests using 31 | // the provided API token for authentication. 32 | func NewAPITokenClient(agentMetadata *api.AgentMetadata, apiToken, baseURL string) (*APITokenClient, error) { 33 | if baseURL == "" { 34 | return nil, fmt.Errorf("cannot create APITokenClient: baseURL cannot be empty") 35 | } 36 | 37 | return &APITokenClient{ 38 | apiToken: apiToken, 39 | agentMetadata: agentMetadata, 40 | baseURL: baseURL, 41 | client: &http.Client{ 42 | Timeout: time.Minute, 43 | Transport: transport.DebugWrappers(http.DefaultTransport), 44 | }, 45 | }, nil 46 | } 47 | 48 | // PostDataReadingsWithOptions uploads the slice of api.DataReading to the Jetstack Secure backend to be processed for later 49 | // viewing in the user-interface. 50 | func (c *APITokenClient) PostDataReadingsWithOptions(ctx context.Context, readings []*api.DataReading, opts Options) error { 51 | return c.PostDataReadings(ctx, opts.OrgID, opts.ClusterID, readings) 52 | } 53 | 54 | // PostDataReadings uploads the slice of api.DataReading to the Jetstack Secure backend to be processed for later 55 | // viewing in the user-interface. 56 | func (c *APITokenClient) PostDataReadings(ctx context.Context, orgID, clusterID string, readings []*api.DataReading) error { 57 | payload := api.DataReadingsPost{ 58 | AgentMetadata: c.agentMetadata, 59 | DataGatherTime: time.Now().UTC(), 60 | DataReadings: readings, 61 | } 62 | data, err := json.Marshal(payload) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | res, err := c.Post(ctx, filepath.Join("/api/v1/org", orgID, "datareadings", clusterID), bytes.NewBuffer(data)) 68 | if err != nil { 69 | return err 70 | } 71 | defer res.Body.Close() 72 | 73 | if code := res.StatusCode; code < 200 || code >= 300 { 74 | errorContent := "" 75 | body, err := io.ReadAll(res.Body) 76 | if err == nil { 77 | errorContent = string(body) 78 | } 79 | 80 | return fmt.Errorf("received response with status code %d. Body: [%s]", code, errorContent) 81 | } 82 | 83 | return nil 84 | } 85 | 86 | // Post performs an HTTP POST request. 87 | func (c *APITokenClient) Post(ctx context.Context, path string, body io.Reader) (*http.Response, error) { 88 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, fullURL(c.baseURL, path), body) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | req.Header.Set("Content-Type", "application/json") 94 | req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiToken)) 95 | version.SetUserAgent(req) 96 | 97 | return c.client.Do(req) 98 | } 99 | -------------------------------------------------------------------------------- /pkg/datagatherer/datagatherer.go: -------------------------------------------------------------------------------- 1 | // Package datagatherer provides the DataGatherer interface. 2 | package datagatherer 3 | 4 | import "context" 5 | 6 | // Config is the configuration of a DataGatherer. 7 | type Config interface { 8 | // NewDataGatherer constructs a DataGatherer with an specific configuration. 9 | NewDataGatherer(ctx context.Context) (DataGatherer, error) 10 | } 11 | 12 | // DataGatherer is the interface for Data Gatherers. Data Gatherers are in charge of fetching data from a certain cloud provider API or Kubernetes component. 13 | type DataGatherer interface { 14 | // Fetch retrieves data. 15 | // count is the number of items that were discovered. A negative count means the number 16 | // of items was indeterminate. 17 | Fetch() (data interface{}, count int, err error) 18 | // Run starts the data gatherer's informers for resource collection. 19 | // Returns error if the data gatherer informer wasn't initialized 20 | Run(stopCh <-chan struct{}) error 21 | // WaitForCacheSync waits for the data gatherer's informers cache to sync. 22 | WaitForCacheSync(stopCh <-chan struct{}) error 23 | // Delete, clear the cache of the DataGatherer if one is being used 24 | Delete() error 25 | } 26 | -------------------------------------------------------------------------------- /pkg/datagatherer/k8s/cache.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/go-logr/logr" 8 | "github.com/pmylund/go-cache" 9 | "k8s.io/apimachinery/pkg/types" 10 | 11 | "github.com/jetstack/preflight/api" 12 | ) 13 | 14 | // time interface, this is used to fetch the current time 15 | // whenever a k8s resource is deleted 16 | type timeInterface interface { 17 | now() time.Time 18 | } 19 | 20 | var clock timeInterface = &realTime{} 21 | 22 | type realTime struct { 23 | } 24 | 25 | func (*realTime) now() time.Time { 26 | return time.Now() 27 | } 28 | 29 | type cacheResource interface { 30 | GetUID() types.UID 31 | GetNamespace() string 32 | } 33 | 34 | func logCacheUpdateFailure(log logr.Logger, obj interface{}, operation string) { 35 | // We use WithCallStackHelper to ensure the correct caller line numbers in the log messages 36 | helper, log := log.WithCallStackHelper() 37 | helper() 38 | err := fmt.Errorf("not a cacheResource type: %T missing metadata/uid field", obj) 39 | log.Error(err, "Cache update failure", "operation", operation) 40 | } 41 | 42 | // onAdd handles the informer creation events, adding the created runtime.Object 43 | // to the data gatherer's cache. The cache key is the uid of the object 44 | func onAdd(log logr.Logger, obj interface{}, dgCache *cache.Cache) { 45 | item, ok := obj.(cacheResource) 46 | if ok { 47 | cacheObject := &api.GatheredResource{ 48 | Resource: obj, 49 | } 50 | dgCache.Set(string(item.GetUID()), cacheObject, cache.DefaultExpiration) 51 | return 52 | } 53 | logCacheUpdateFailure(log, obj, "add") 54 | } 55 | 56 | // onUpdate handles the informer update events, replacing the old object with the new one 57 | // if it's present in the data gatherer's cache, (if the object isn't present, it gets added). 58 | // The cache key is the uid of the object 59 | func onUpdate(log logr.Logger, old, new interface{}, dgCache *cache.Cache) { 60 | item, ok := old.(cacheResource) 61 | if ok { 62 | cacheObject := updateCacheGatheredResource(string(item.GetUID()), new, dgCache) 63 | dgCache.Set(string(item.GetUID()), cacheObject, cache.DefaultExpiration) 64 | return 65 | } 66 | logCacheUpdateFailure(log, old, "update") 67 | } 68 | 69 | // onDelete handles the informer deletion events, updating the object's properties with the deletion 70 | // time of the object (but not removing the object from the cache). 71 | // The cache key is the uid of the object 72 | func onDelete(log logr.Logger, obj interface{}, dgCache *cache.Cache) { 73 | item, ok := obj.(cacheResource) 74 | if ok { 75 | cacheObject := updateCacheGatheredResource(string(item.GetUID()), obj, dgCache) 76 | cacheObject.DeletedAt = api.Time{Time: clock.now()} 77 | dgCache.Set(string(item.GetUID()), cacheObject, cache.DefaultExpiration) 78 | return 79 | } 80 | logCacheUpdateFailure(log, obj, "delete") 81 | } 82 | 83 | // creates a new updated instance of a cache object, with the resource 84 | // argument. If the object is present in the cache it fetches the object's 85 | // properties. 86 | func updateCacheGatheredResource(cacheKey string, resource interface{}, dgCache *cache.Cache) *api.GatheredResource { 87 | // updated cache object 88 | cacheObject := &api.GatheredResource{ 89 | Resource: resource, 90 | } 91 | // update the object's properties, if it's already in the cache 92 | if o, ok := dgCache.Get(cacheKey); ok { 93 | deletedAt := o.(*api.GatheredResource).DeletedAt 94 | if deletedAt.IsZero() && !deletedAt.IsZero() { 95 | cacheObject.DeletedAt = deletedAt 96 | } 97 | } 98 | return cacheObject 99 | } 100 | -------------------------------------------------------------------------------- /pkg/datagatherer/k8s/client.go: -------------------------------------------------------------------------------- 1 | // Package k8s provides datagatherers for different parts of the Kubernetes API. 2 | package k8s 3 | 4 | import ( 5 | "github.com/pkg/errors" 6 | "k8s.io/client-go/discovery" 7 | "k8s.io/client-go/dynamic" 8 | "k8s.io/client-go/kubernetes" 9 | 10 | "github.com/jetstack/preflight/pkg/kubeconfig" 11 | ) 12 | 13 | // NewDynamicClient creates a new 'dynamic' clientset using the provided kubeconfig. 14 | // If kubeconfigPath is not set/empty, it will attempt to load configuration using 15 | // the default loading rules. 16 | func NewDynamicClient(kubeconfigPath string) (dynamic.Interface, error) { 17 | cfg, err := kubeconfig.LoadRESTConfig(kubeconfigPath) 18 | if err != nil { 19 | return nil, errors.WithStack(err) 20 | } 21 | 22 | cl, err := dynamic.NewForConfig(cfg) 23 | if err != nil { 24 | return nil, errors.WithStack(err) 25 | } 26 | 27 | return cl, nil 28 | } 29 | 30 | // NewDiscoveryClient creates a new 'discovery' client using the provided 31 | // kubeconfig. If kubeconfigPath is not set/empty, it will attempt to load 32 | // configuration using the default loading rules. 33 | func NewDiscoveryClient(kubeconfigPath string) (*discovery.DiscoveryClient, error) { 34 | cfg, err := kubeconfig.LoadRESTConfig(kubeconfigPath) 35 | if err != nil { 36 | return nil, errors.WithStack(err) 37 | } 38 | 39 | discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg) 40 | if err != nil { 41 | return nil, errors.WithStack(err) 42 | } 43 | 44 | return discoveryClient, nil 45 | } 46 | 47 | // NewClientSet creates a new kubernetes clientset using the provided kubeconfig. 48 | // If kubeconfigPath is not set/empty, it will attempt to load configuration using 49 | // the default loading rules. 50 | func NewClientSet(kubeconfigPath string) (kubernetes.Interface, error) { 51 | cfg, err := kubeconfig.LoadRESTConfig(kubeconfigPath) 52 | if err != nil { 53 | return nil, errors.WithStack(err) 54 | } 55 | 56 | clientset, err := kubernetes.NewForConfig(cfg) 57 | if err != nil { 58 | return nil, errors.WithStack(err) 59 | } 60 | 61 | return clientset, nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/datagatherer/k8s/client_test.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 8 | clientcmdlatest "k8s.io/client-go/tools/clientcmd/api/latest" 9 | ) 10 | 11 | // These tests do not currently validate the created dynamic client uses the 12 | // KUBECONFIG file that we create, however it _does_ exercise enough of the 13 | // code path to show that the function is correctly selecting which file to 14 | // load and returning it. 15 | 16 | func TestNewDynamicClient_ExplicitKubeconfig(t *testing.T) { 17 | kc := createValidTestConfig() 18 | path := writeConfigToFile(t, kc) 19 | _, err := NewDynamicClient(path) 20 | if err != nil { 21 | t.Error("failed to create client: ", err) 22 | } 23 | } 24 | 25 | func TestNewDynamicClient_InferredKubeconfig(t *testing.T) { 26 | kc := createValidTestConfig() 27 | path := writeConfigToFile(t, kc) 28 | cleanupFn := temporarilySetEnv("KUBECONFIG", path) 29 | defer cleanupFn() 30 | _, err := NewDynamicClient("") 31 | if err != nil { 32 | t.Error("failed to create client: ", err) 33 | } 34 | } 35 | 36 | func TestNewDiscoveryClient_ExplicitKubeconfig(t *testing.T) { 37 | kc := createValidTestConfig() 38 | path := writeConfigToFile(t, kc) 39 | _, err := NewDiscoveryClient(path) 40 | if err != nil { 41 | t.Error("failed to create client: ", err) 42 | } 43 | } 44 | 45 | func TestNewDiscoveryClient_InferredKubeconfig(t *testing.T) { 46 | kc := createValidTestConfig() 47 | path := writeConfigToFile(t, kc) 48 | cleanupFn := temporarilySetEnv("KUBECONFIG", path) 49 | defer cleanupFn() 50 | _, err := NewDiscoveryClient("") 51 | if err != nil { 52 | t.Error("failed to create client: ", err) 53 | } 54 | } 55 | 56 | func writeConfigToFile(t *testing.T, cfg clientcmdapi.Config) string { 57 | f, err := os.CreateTemp(t.TempDir(), "testcase-*") 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | defer f.Close() 62 | if err := clientcmdlatest.Codec.Encode(&cfg, f); err != nil { 63 | t.Fatal(err) 64 | } 65 | return f.Name() 66 | } 67 | 68 | func createValidTestConfig() clientcmdapi.Config { 69 | const ( 70 | server = "https://example.com:8080" 71 | token = "the-token" 72 | ) 73 | 74 | config := clientcmdapi.NewConfig() 75 | config.Clusters["clean"] = &clientcmdapi.Cluster{ 76 | Server: server, 77 | } 78 | config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 79 | Token: token, 80 | } 81 | config.Contexts["clean"] = &clientcmdapi.Context{ 82 | Cluster: "clean", 83 | AuthInfo: "clean", 84 | } 85 | config.CurrentContext = "clean" 86 | 87 | return *config 88 | } 89 | 90 | func temporarilySetEnv(key, value string) func() { 91 | old := os.Getenv(key) 92 | os.Setenv(key, value) 93 | return func() { 94 | os.Setenv(key, old) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /pkg/datagatherer/k8s/discovery.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "k8s.io/client-go/discovery" 8 | 9 | "github.com/jetstack/preflight/pkg/datagatherer" 10 | ) 11 | 12 | // ConfigDiscovery contains the configuration for the k8s-discovery data-gatherer 13 | type ConfigDiscovery struct { 14 | // KubeConfigPath is the path to the kubeconfig file. If empty, will assume it runs in-cluster. 15 | KubeConfigPath string `yaml:"kubeconfig"` 16 | } 17 | 18 | // UnmarshalYAML unmarshals the Config resolving GroupVersionResource. 19 | func (c *ConfigDiscovery) UnmarshalYAML(unmarshal func(interface{}) error) error { 20 | aux := struct { 21 | KubeConfigPath string `yaml:"kubeconfig"` 22 | }{} 23 | err := unmarshal(&aux) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | c.KubeConfigPath = aux.KubeConfigPath 29 | 30 | return nil 31 | } 32 | 33 | // NewDataGatherer constructs a new instance of the generic K8s data-gatherer for the provided 34 | // GroupVersionResource. 35 | func (c *ConfigDiscovery) NewDataGatherer(ctx context.Context) (datagatherer.DataGatherer, error) { 36 | cl, err := NewDiscoveryClient(c.KubeConfigPath) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return &DataGathererDiscovery{cl: cl}, nil 42 | } 43 | 44 | // DataGathererDiscovery stores the config for a k8s-discovery datagatherer 45 | type DataGathererDiscovery struct { 46 | // The 'discovery' client used for fetching data. 47 | cl *discovery.DiscoveryClient 48 | } 49 | 50 | func (g *DataGathererDiscovery) Run(stopCh <-chan struct{}) error { 51 | // no async functionality, see Fetch 52 | return nil 53 | } 54 | 55 | func (g *DataGathererDiscovery) WaitForCacheSync(stopCh <-chan struct{}) error { 56 | // no async functionality, see Fetch 57 | return nil 58 | } 59 | 60 | func (g *DataGathererDiscovery) Delete() error { 61 | // no async functionality, see Fetch 62 | return nil 63 | } 64 | 65 | // Fetch will fetch discovery data from the apiserver, or return an error 66 | func (g *DataGathererDiscovery) Fetch() (interface{}, int, error) { 67 | data, err := g.cl.ServerVersion() 68 | if err != nil { 69 | return nil, -1, fmt.Errorf("failed to get server version: %v", err) 70 | } 71 | 72 | response := map[string]interface{}{ 73 | // data has type Info: https://godoc.org/k8s.io/apimachinery/pkg/version#Info 74 | "server_version": data, 75 | } 76 | 77 | return response, len(response), nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/datagatherer/k8s/fieldfilter.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 5 | ) 6 | 7 | // SecretSelectedFields is the list of fields sent from Secret objects to the 8 | // backend 9 | var SecretSelectedFields = []FieldPath{ 10 | {"kind"}, 11 | {"apiVersion"}, 12 | {"metadata", "annotations"}, 13 | {"metadata", "labels"}, 14 | {"metadata", "name"}, 15 | {"metadata", "namespace"}, 16 | {"metadata", "ownerReferences"}, 17 | {"metadata", "selfLink"}, 18 | {"metadata", "uid"}, 19 | 20 | {"type"}, 21 | {"data", "tls.crt"}, 22 | {"data", "ca.crt"}, 23 | } 24 | 25 | // RouteSelectedFields is the list of fields sent from OpenShift Route objects to the 26 | // backend 27 | var RouteSelectedFields = []FieldPath{ 28 | {"kind"}, 29 | {"apiVersion"}, 30 | {"metadata", "annotations"}, 31 | {"metadata", "name"}, 32 | {"metadata", "namespace"}, 33 | {"metadata", "ownerReferences"}, 34 | {"metadata", "selfLink"}, 35 | {"metadata", "uid"}, 36 | 37 | {"spec", "host"}, 38 | {"spec", "to", "kind"}, 39 | {"spec", "to", "name"}, 40 | {"spec", "to", "weight"}, 41 | {"spec", "tls", "termination"}, 42 | {"spec", "tls", "certificate"}, 43 | {"spec", "tls", "caCertificate"}, 44 | {"spec", "tls", "destinationCACertificate"}, 45 | {"spec", "tls", "insecureEdgeTerminationPolicy"}, 46 | {"spec", "wildcardPolicy"}, 47 | {"status"}, 48 | } 49 | 50 | // RedactFields are removed from all objects 51 | var RedactFields = []FieldPath{ 52 | {"metadata", "managedFields"}, 53 | {"metadata", "annotations", "kubectl.kubernetes.io/last-applied-configuration"}, 54 | } 55 | 56 | type FieldPath []string 57 | 58 | // Select removes all but the supplied fields from the resource 59 | func Select(fields []FieldPath, resource *unstructured.Unstructured) error { 60 | newResource := unstructured.Unstructured{ 61 | Object: map[string]interface{}{}, 62 | } 63 | 64 | for _, field := range fields { 65 | value, found, err := unstructured.NestedFieldNoCopy(resource.Object, field...) 66 | if err != nil { 67 | return err 68 | } 69 | if !found { 70 | continue 71 | } 72 | if err := unstructured.SetNestedField(newResource.Object, value, field...); err != nil { 73 | return err 74 | } 75 | } 76 | 77 | resource.Object = newResource.Object 78 | 79 | return nil 80 | } 81 | 82 | // Redact removes the supplied fields from the resource 83 | func Redact(fields []FieldPath, resource *unstructured.Unstructured) error { 84 | for _, field := range fields { 85 | unstructured.RemoveNestedField(resource.Object, field...) 86 | } 87 | 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /pkg/datagatherer/local/local.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/jetstack/preflight/pkg/datagatherer" 9 | ) 10 | 11 | // Config is the configuration for a local DataGatherer. 12 | type Config struct { 13 | // DataPath is the path to file containing the data to load. 14 | DataPath string `yaml:"data-path"` 15 | } 16 | 17 | // validate validates the configuration. 18 | func (c *Config) validate() error { 19 | if c.DataPath == "" { 20 | return fmt.Errorf("invalid configuration: DataPath cannot be empty") 21 | } 22 | return nil 23 | } 24 | 25 | // DataGatherer is a data-gatherer that loads data from a local file. 26 | type DataGatherer struct { 27 | dataPath string 28 | } 29 | 30 | // NewDataGatherer returns a new DataGatherer. 31 | func (c *Config) NewDataGatherer(ctx context.Context) (datagatherer.DataGatherer, error) { 32 | if err := c.validate(); err != nil { 33 | return nil, err 34 | } 35 | 36 | return &DataGatherer{ 37 | dataPath: c.DataPath, 38 | }, nil 39 | } 40 | 41 | func (g *DataGatherer) Run(stopCh <-chan struct{}) error { 42 | // no async functionality, see Fetch 43 | return nil 44 | } 45 | 46 | func (g *DataGatherer) Delete() error { 47 | // no async functionality, see Fetch 48 | return nil 49 | } 50 | 51 | func (g *DataGatherer) WaitForCacheSync(stopCh <-chan struct{}) error { 52 | // no async functionality, see Fetch 53 | return nil 54 | } 55 | 56 | // Fetch loads and returns the data from the LocalDatagatherer's dataPath 57 | func (g *DataGatherer) Fetch() (interface{}, int, error) { 58 | dataBytes, err := os.ReadFile(g.dataPath) 59 | if err != nil { 60 | return nil, -1, err 61 | } 62 | return dataBytes, -1, nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/echo/echo.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/fatih/color" 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/jetstack/preflight/api" 12 | ) 13 | 14 | var EchoListen string 15 | 16 | var Compact bool 17 | 18 | func Echo(cmd *cobra.Command, args []string) error { 19 | http.HandleFunc("/", echoHandler) 20 | fmt.Println("Listening to requests at ", EchoListen) 21 | return http.ListenAndServe(EchoListen, nil) 22 | } 23 | 24 | func echoHandler(w http.ResponseWriter, r *http.Request) { 25 | if r.Method != http.MethodPost { 26 | writeError(w, fmt.Sprintf("invalid method. Expected POST, received %s", r.Method), http.StatusBadRequest) 27 | return 28 | } 29 | 30 | // decode all data, however only datareadings are printed below 31 | var payload api.DataReadingsPost 32 | err := json.NewDecoder(r.Body).Decode(&payload) 33 | if err != nil { 34 | writeError(w, fmt.Sprintf("decoding body: %+v", err), http.StatusBadRequest) 35 | return 36 | } 37 | 38 | // print the data sent to the echo server to the console 39 | 40 | if Compact { 41 | fmt.Printf("-- %s %s -> created %d\n", r.Method, r.URL.Path, http.StatusCreated) 42 | fmt.Printf("received %d readings:\n", len(payload.DataReadings)) 43 | for _, r := range payload.DataReadings { 44 | fmt.Printf("%+v\n", r) 45 | } 46 | } else { 47 | color.Green("-- %s %s -> created %d\n", r.Method, r.URL.Path, http.StatusCreated) 48 | fmt.Printf("received %d readings:\n", len(payload.DataReadings)) 49 | 50 | for i, r := range payload.DataReadings { 51 | c := color.New(color.FgYellow) 52 | if i%2 == 0 { 53 | c = color.New(color.FgCyan) 54 | } 55 | 56 | c.Printf("%v:\n%s\n", i, prettyPrint(r)) 57 | } 58 | 59 | color.Green("-----") 60 | } 61 | 62 | // return successful response to the agent 63 | fmt.Fprintf(w, `{ "status": "ok" }`) 64 | w.Header().Set("Content-Type", "application/json") 65 | } 66 | 67 | func writeError(w http.ResponseWriter, err string, code int) { 68 | fmt.Printf("-- error %d -> %s\n", code, err) 69 | w.Header().Set("Content-Type", "application/json") 70 | http.Error(w, fmt.Sprintf(`{ "error": "%s", "code": %d }`, err, code), code) 71 | } 72 | 73 | func prettyPrint(reading *api.DataReading) string { 74 | return fmt.Sprintf(`ClusterID: %s 75 | Data gatherer: %s 76 | Timestamp: %s 77 | SchemaVersion: %s 78 | Data: %+v`, 79 | reading.ClusterID, reading.DataGatherer, reading.Timestamp, reading.SchemaVersion, reading.Data) 80 | } 81 | -------------------------------------------------------------------------------- /pkg/echo/echo_test.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | "time" 10 | 11 | "github.com/jetstack/preflight/api" 12 | ) 13 | 14 | type testInput struct { 15 | description string 16 | data *api.DataReadingsPost 17 | exp int 18 | method string 19 | } 20 | 21 | func TestEchoServerRequestResponse(t *testing.T) { 22 | // create sample data in same format that would be generated by the agent 23 | sampleUploadCases := []testInput{ 24 | { 25 | description: "correct request input should return status code 200", 26 | data: &api.DataReadingsPost{ 27 | AgentMetadata: &api.AgentMetadata{ 28 | Version: "test suite", 29 | ClusterID: "test_suite_cluster", 30 | }, 31 | DataGatherTime: time.Now(), 32 | DataReadings: []*api.DataReading{ 33 | { 34 | ClusterID: "test_suite_cluster", 35 | DataGatherer: "dummy", 36 | Timestamp: api.Time{Time: time.Now()}, 37 | Data: map[string]string{ 38 | "test": "test", 39 | }, 40 | SchemaVersion: "2.0.0", 41 | }, 42 | }, 43 | }, 44 | exp: http.StatusOK, 45 | method: "POST", 46 | }, 47 | { 48 | description: "sending GET request should return status code 400", 49 | method: "GET", 50 | data: nil, 51 | exp: http.StatusBadRequest, 52 | }, 53 | } 54 | 55 | for _, sampleUpload := range sampleUploadCases { 56 | // generate the JSON representation of the data to be sent to the echo server 57 | requestBodyJSON, err := json.Marshal(sampleUpload.data) 58 | if err != nil { 59 | t.Fatalf("[%s]\nfailed to generate JSON request body to post: %s", sampleUpload.description, err) 60 | } 61 | 62 | // generate a request to test the handler containing the JSON data as a body 63 | req, err := http.NewRequest(sampleUpload.method, "http://example.com/api/v1/datareadings", bytes.NewBuffer(requestBodyJSON)) 64 | if err != nil { 65 | t.Fatalf("[%s]\nfailed to generate request to test echo server: %s", sampleUpload.description, err) 66 | } 67 | 68 | // create recorder to save the response 69 | rr := httptest.NewRecorder() 70 | 71 | // perform the request with the handler 72 | echoHandler(rr, req) 73 | 74 | // Check the response from the echo handler is the expected one 75 | response := rr.Result() 76 | if response.StatusCode != sampleUpload.exp { 77 | t.Fatalf("[%s]\necho server responded with an unexpected code: %d", sampleUpload.description, response.StatusCode) 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /pkg/internal/cyberark/servicediscovery/testdata/README.md: -------------------------------------------------------------------------------- 1 | # Test data for CyberArk Discovery 2 | 3 | All data in this folder is derived from an unauthenticated endpoint accessible from the public internet. 4 | -------------------------------------------------------------------------------- /pkg/internal/cyberark/servicediscovery/testdata/discovery_success.json: -------------------------------------------------------------------------------- 1 | {"data_privacy": {"ui": "https://ui.dataprivacy.integration-cyberark.cloud/", "api": "https://us-east-1.dataprivacy.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-data_privacy.integration-cyberark.cloud", "region": "us-east-1"}, "secrets_manager": {"ui": "https://ui.test-conjur.cloud", "api": "https://venafi-test.secretsmgr.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-secrets_manager.integration-cyberark.cloud", "region": "us-east-2"}, "idaptive_risk_analytics": {"ui": "https://ajp5871-my.analytics.idaptive.qa", "api": "https://ajp5871-my.analytics.idaptive.qa", "bootstrap": "https://venafi-test-idaptive_risk_analytics.integration-cyberark.cloud", "region": "US-East-Pod"}, "component_manager": {"ui": "https://ui-connectormanagement.connectormanagement.integration-cyberark.cloud", "api": "https://venafi-test.connectormanagement.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-component_manager.integration-cyberark.cloud", "region": "us-east-1"}, "recording": {"ui": "https://us-east-1.rec-ui.recording.integration-cyberark.cloud", "api": "https://venafi-test.recording.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-recording.integration-cyberark.cloud", "region": "us-east-1"}, "identity_user_portal": {"ui": "https://ajp5871.id.integration-cyberark.cloud", "api": "https://ajp5871.id.integration-cyberark.cloud", "bootstrap": "https://venafi-test-identity_user_portal.integration-cyberark.cloud/my", "region": "US-East-Pod"}, "userportal": {"ui": "https://us-east-1.ui.userportal.integration-cyberark.cloud/", "api": "https://venafi-test.api.userportal.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-userportal.integration-cyberark.cloud", "region": "us-east-1"}, "cloud_onboarding": {"ui": "https://ui-cloudonboarding.cloudonboarding.integration-cyberark.cloud/", "api": "https://venafi-test.cloudonboarding.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-cloud_onboarding.integration-cyberark.cloud", "region": "us-east-1"}, "identity_administration": {"ui": "https://ajp5871.id.integration-cyberark.cloud", "api": "https://ajp5871.id.integration-cyberark.cloud", "bootstrap": "https://venafi-test-identity_administration.integration-cyberark.cloud/admin", "region": "US-East-Pod"}, "adminportal": {"ui": "https://ui-adminportal.adminportal.integration-cyberark.cloud", "api": "https://venafi-test.adminportal.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-adminportal.integration-cyberark.cloud", "region": "us-east-1"}, "analytics": {"ui": "https://venafi-test.analytics.integration-cyberark.cloud/", "api": "https://venafi-test.analytics.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-analytics.integration-cyberark.cloud", "region": "us-east-1"}, "session_monitoring": {"ui": "https://us-east-1.sm-ui.sessionmonitoring.integration-cyberark.cloud", "api": "https://venafi-test.sessionmonitoring.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-session_monitoring.integration-cyberark.cloud", "region": "us-east-1"}, "audit": {"ui": "https://ui.audit-ui.integration-cyberark.cloud", "api": "https://venafi-test.audit.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-audit.integration-cyberark.cloud", "region": "us-east-1"}, "fmcdp": {"ui": "https://tagtig.io/", "api": "https://tagtig.io/api", "bootstrap": "https://venafi-test-fmcdp.integration-cyberark.cloud", "region": "us-east-1"}, "featureadopt": {"ui": "https://ui-featureadopt.featureadopt.integration-cyberark.cloud/", "api": "https://us-east-1-featureadopt.featureadopt.integration-cyberark.cloud/api", "bootstrap": "https://venafi-test-featureadopt.integration-cyberark.cloud", "region": "us-east-1"}} -------------------------------------------------------------------------------- /pkg/kubeconfig/kubeconfig.go: -------------------------------------------------------------------------------- 1 | package kubeconfig 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "k8s.io/client-go/rest" 6 | "k8s.io/client-go/tools/clientcmd" 7 | ) 8 | 9 | // LoadRESTConfig loads the kube config from the provided path. If the path is 10 | // empty, the kube config will be loaded from KUBECONFIG, and if KUBECONFIG 11 | // isn't set, the in-cluster config will be used. 12 | func LoadRESTConfig(path string) (*rest.Config, error) { 13 | loadingrules := clientcmd.NewDefaultClientConfigLoadingRules() 14 | 15 | // If the kubeconfig path is provided, use that file and fail if it does 16 | // not exist. 17 | // If the kubeconfig path is not provided, use the default loading rules 18 | // so we read the regular KUBECONFIG variable or create a non-interactive 19 | // client for agents running in cluster 20 | loadingrules.ExplicitPath = path 21 | 22 | cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 23 | loadingrules, 24 | &clientcmd.ConfigOverrides{}, 25 | ).ClientConfig() 26 | if err != nil { 27 | return nil, errors.WithStack(err) 28 | } 29 | 30 | return cfg, nil 31 | } 32 | -------------------------------------------------------------------------------- /pkg/testutil/undent_test.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | // This is a test for the testing func "Undent". I wasn't confident with 10 | // Undent's behavior, so I wrote this test to verify it. 11 | func Test_Undent(t *testing.T) { 12 | t.Run("empty string", runTest_Undent(``, ``)) 13 | 14 | t.Run("if last line has the same indent as other lines and, it is ignored", runTest_Undent(` 15 | foo 16 | bar 17 | `, "foo\nbar\n")) 18 | 19 | t.Run("you can un-indent the last line to make the Go code more readable", runTest_Undent(` 20 | foo 21 | bar 22 | `, "foo\nbar\n")) 23 | 24 | t.Run("last line may not be an empty line", runTest_Undent(` 25 | foo 26 | bar`, "foo\nbar")) 27 | 28 | t.Run("1 empty line is preserved", runTest_Undent("\t\tfoo\n\t\t\n\t\tbar\n", "foo\n\nbar\n")) 29 | 30 | t.Run("2 empty lines are preserved", runTest_Undent("\t\tfoo\n\t\t\n\t\t\n\t\tbar\n", "foo\n\n\nbar\n")) 31 | 32 | t.Run("you can also omit the tabs or spaces for empty lines", runTest_Undent(` 33 | foo 34 | 35 | bar 36 | `, "foo\n\nbar\n")) 37 | t.Run("bug fix: last char is not omitted", runTest_Undent("\t\t{\n\t\t \"kind\": \"Secret\"\n\t\t}", "{\n \"kind\": \"Secret\"\n}")) 38 | } 39 | 40 | func runTest_Undent(given, expected string) func(t *testing.T) { 41 | return func(t *testing.T) { 42 | t.Helper() 43 | got := Undent(given) 44 | assert.Equal(t, expected, got) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // This variables are injected at build time. 9 | 10 | // PreflightVersion hosts the version of the app. 11 | var PreflightVersion = "development" 12 | 13 | // Commit is the commit hash of the build 14 | var Commit string 15 | 16 | // BuildDate is the date it was built 17 | var BuildDate string 18 | 19 | // GoVersion is the go version that was used to compile this 20 | var GoVersion string 21 | 22 | // UserAgent return a standard user agent for use with all HTTP requests. This is implemented in one place so 23 | // it's uniform across the Kubernetes Agent. 24 | func UserAgent() string { 25 | return fmt.Sprintf("venafi-kubernetes-agent/%s", PreflightVersion) 26 | } 27 | 28 | // SetUserAgent augments an http.Request with a standard user agent. 29 | func SetUserAgent(req *http.Request) { 30 | req.Header.Set("User-Agent", UserAgent()) 31 | } 32 | --------------------------------------------------------------------------------