diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..e0871f93 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index 538aed15..00000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,4 +0,0 @@ -needs triage: - - '**' # index.php | src/main.php - - '.*' # .gitignore - - '.*/**' # .github/workflows/label.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..6273c13e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + tags-ignore: + - 'v*' + branches: + - "master" + pull_request: + workflow_call: + secrets: + CODECOV_TOKEN: + required: true + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + ci: + uses: smallstep/workflows/.github/workflows/goCI.yml@main + with: + os-dependencies: "libpcsclite-dev" + run-gitleaks: true + run-codeql: true + secrets: inherit diff --git a/.github/workflows/code-scan-cron.yml b/.github/workflows/code-scan-cron.yml new file mode 100644 index 00000000..56969c11 --- /dev/null +++ b/.github/workflows/code-scan-cron.yml @@ -0,0 +1,9 @@ +on: + schedule: + - cron: '0 0 * * *' + +jobs: + code-scan: + uses: smallstep/workflows/.github/workflows/code-scan.yml@main + secrets: + GITLEAKS_LICENSE_KEY: ${{ secrets.GITLEAKS_LICENSE_KEY }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index d23cddf9..00000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,72 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ "master" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "master" ] - schedule: - - cron: '30 3 * * 3' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4336472..f66ad67b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,63 +7,40 @@ on: - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 jobs: - test: - name: Lint, Test, Build - runs-on: ubuntu-20.04 - strategy: - matrix: - go: [ '1.18', '1.19' ] - outputs: - is_prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }} - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go }} - - - name: Install Deps - id: install-deps - run: sudo apt-get -y install libpcsclite-dev - - - name: golangci-lint - uses: golangci/golangci-lint-action@v2 - with: - version: ${{ secrets.GOLANGCI_LINT_VERSION }} - args: --timeout=30m - - - name: Test, Build - id: lint_test_build - run: V=1 make ci + ci: + uses: smallstep/certificates/.github/workflows/ci.yml@master + secrets: inherit create_release: name: Create Release - needs: test - runs-on: ubuntu-20.04 + needs: ci + runs-on: ubuntu-latest + env: + DOCKER_IMAGE: smallstep/step-ca outputs: - debversion: ${{ steps.extract-tag.outputs.DEB_VERSION }} + version: ${{ steps.extract-tag.outputs.VERSION }} is_prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }} + docker_tags: ${{ env.DOCKER_TAGS }} steps: - - - name: Extract Tag Names - id: extract-tag - run: | - DEB_VERSION=$(echo ${GITHUB_REF#refs/tags/v} | sed 's/-/./') - echo "::set-output name=DEB_VERSION::${DEB_VERSION}" - - - name: Is Pre-release + - name: Is Pre-release id: is_prerelease run: | set +e echo ${{ github.ref }} | grep "\-rc.*" OUT=$? if [ $OUT -eq 0 ]; then IS_PRERELEASE=true; else IS_PRERELEASE=false; fi - echo "::set-output name=IS_PRERELEASE::${IS_PRERELEASE}" - - - name: Create Release + echo "IS_PRERELEASE=${IS_PRERELEASE}" >> ${GITHUB_OUTPUT} + - name: Extract Tag Names + id: extract-tag + run: | + VERSION=${GITHUB_REF#refs/tags/v} + echo "VERSION=${VERSION}" >> ${GITHUB_OUTPUT} + echo "DOCKER_TAGS=${{ env.DOCKER_IMAGE }}:${VERSION}" >> ${GITHUB_ENV} + - name: Add Latest Tag + if: steps.is_prerelease.outputs.IS_PRERELEASE == 'false' + run: | + echo "DOCKER_TAGS=${{ env.DOCKER_TAGS }},${{ env.DOCKER_IMAGE }}:latest" >> ${GITHUB_ENV} + - name: Create Release id: create_release uses: actions/create-release@v1 env: @@ -76,88 +53,48 @@ jobs: goreleaser: name: Upload Assets To Github w/ goreleaser - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: create_release + permissions: + id-token: write + contents: write steps: - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v2 + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v3 with: go-version: 1.19 - - - name: APT Install - id: aptInstall - run: sudo apt-get -y install build-essential debhelper fakeroot - - - name: Build Debian package - id: make_debian - run: | - PATH=$PATH:/usr/local/go/bin:/home/admin/go/bin - make debian - # need to restore the git state otherwise goreleaser fails due to dirty state - git restore debian/changelog - git clean -fd - - - name: Install cosign - uses: sigstore/cosign-installer@v1.1.0 + check-latest: true + - name: Install cosign + uses: sigstore/cosign-installer@v2 with: - cosign-release: 'v1.1.0' - - - name: Write cosign key to disk - id: write_key - run: echo "${{ secrets.COSIGN_KEY }}" > "/tmp/cosign.key" - - - name: Get Release Date + cosign-release: 'v1.13.1' + - name: Get Release Date id: release_date run: | RELEASE_DATE=$(date +"%y-%m-%d") - echo "::set-output name=RELEASE_DATE::${RELEASE_DATE}" - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@5a54d7e660bda43b405e8463261b3d25631ffe86 # v2.7.0 + echo "RELEASE_DATE=${RELEASE_DATE}" >> ${GITHUB_ENV} + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v3 with: - version: 'v1.7.0' + version: 'latest' args: release --rm-dist env: - GITHUB_TOKEN: ${{ secrets.PAT }} - COSIGN_PWD: ${{ secrets.COSIGN_PWD }} - DEB_VERSION: ${{ needs.create_release.outputs.debversion }} - RELEASE_DATE: ${{ steps.release_date.outputs.RELEASE_DATE }} + GITHUB_TOKEN: ${{ secrets.GORELEASER_PAT }} + RELEASE_DATE: ${{ env.RELEASE_DATE }} + COSIGN_EXPERIMENTAL: 1 build_upload_docker: name: Build & Upload Docker Images - runs-on: ubuntu-20.04 - needs: test - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: '1.19' - - - name: Install cosign - uses: sigstore/cosign-installer@v1.1.0 - with: - cosign-release: 'v1.1.0' - - - name: Write cosign key to disk - id: write_key - run: echo "${{ secrets.COSIGN_KEY }}" > "/tmp/cosign.key" - - - name: Build - id: build - run: | - PATH=$PATH:/usr/local/go/bin:/home/admin/go/bin - make docker-artifacts - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - COSIGN_PWD: ${{ secrets.COSIGN_PWD }} + needs: create_release + permissions: + id-token: write + contents: write + uses: smallstep/workflows/.github/workflows/docker-buildx-push.yml@main + with: + platforms: linux/amd64,linux/386,linux/arm,linux/arm64 + tags: ${{ needs.create_release.outputs.docker_tags }} + docker_image: smallstep/step-ca + docker_file: docker/Dockerfile.step-ca + secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 8a2f391c..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Lint, Test, Build - -on: - push: - tags-ignore: - - 'v*' - branches: - - "**" - pull_request: - -jobs: - lintTestBuild: - name: Lint, Test, Build - runs-on: ubuntu-20.04 - strategy: - matrix: - go: [ '1.18', '1.19' ] - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go }} - - - name: Install Deps - id: install-deps - run: sudo apt-get -y install libpcsclite-dev - - - name: golangci-lint - uses: golangci/golangci-lint-action@v2 - with: - version: ${{ secrets.GOLANGCI_LINT_VERSION }} - args: --timeout=30m - - - name: Test, Build - id: lint_test_build - run: V=1 make ci - - - name: Codecov - if: matrix.go == '1.19' - uses: codecov/codecov-action@v2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage.out # optional - name: codecov-umbrella # optional - fail_ci_if_error: true # optional (default = false) diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index c16aacd8..f1363a4b 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -4,26 +4,13 @@ on: issues: types: - opened + - reopened pull_request_target: types: - opened + - reopened jobs: - - label: - name: Label PR - runs-on: ubuntu-latest - if: github.event_name == 'pull_request_target' - steps: - - uses: actions/labeler@v3.0.2 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" - - add-to-project: - name: Add to Triage Project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@v0.3.0 - with: - project-url: https://github.com/orgs/smallstep/projects/94 - github-token: ${{ secrets.TRIAGE_PAT }} + triage: + uses: smallstep/workflows/.github/workflows/triage.yml@main + secrets: inherit diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 00000000..71318c8a --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,18 @@ +deac15327f5605a1a963e50818760a95cee9d882:docs/kms.md:generic-api-key:85 +deac15327f5605a1a963e50818760a95cee9d882:docs/kms.md:generic-api-key:107 +deac15327f5605a1a963e50818760a95cee9d882:docs/kms.md:generic-api-key:108 +deac15327f5605a1a963e50818760a95cee9d882:docs/kms.md:generic-api-key:129 +deac15327f5605a1a963e50818760a95cee9d882:docs/kms.md:generic-api-key:131 +deac15327f5605a1a963e50818760a95cee9d882:docs/kms.md:generic-api-key:136 +deac15327f5605a1a963e50818760a95cee9d882:docs/kms.md:generic-api-key:138 +7c9ab9814fb676cb3c125c3dac4893271f1b7ae5:README.md:generic-api-key:282 +fb7140444ac8f1fa1245a80e49d17e206f7435f3:docs/provisioners.md:generic-api-key:110 +e4de7f07e82118b3f926716666b620db058fa9f7:docs/revocation.md:generic-api-key:73 +e4de7f07e82118b3f926716666b620db058fa9f7:docs/revocation.md:generic-api-key:113 +e4de7f07e82118b3f926716666b620db058fa9f7:docs/revocation.md:generic-api-key:151 +8b2de42e9cf6ce99f53a5049881e1d6077d5d66e:docs/docker.md:generic-api-key:152 +3939e855264117e81531df777a642ea953d325a7:autocert/init/ca/intermediate_ca_key:private-key:1 +e72f08703753facfa05f2d8c68f9f6a3745824b8:README.md:generic-api-key:244 +e70a5dae7de0b6ca40a0393c09c28872d4cfa071:autocert/README.md:generic-api-key:365 +e70a5dae7de0b6ca40a0393c09c28872d4cfa071:autocert/README.md:generic-api-key:366 +c284a2c0ab1c571a46443104be38c873ef0c7c6d:config.json:generic-api-key:10 diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index af723230..00000000 --- a/.golangci.yml +++ /dev/null @@ -1,74 +0,0 @@ -linters-settings: - govet: - check-shadowing: true - settings: - printf: - funcs: - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - revive: - min-confidence: 0 - gocyclo: - min-complexity: 10 - maligned: - suggest-new: true - dupl: - threshold: 100 - goconst: - min-len: 2 - min-occurrences: 2 - depguard: - list-type: blacklist - packages: - # logging is allowed only by logutils.Log, logrus - # is allowed to use only in logutils package - - github.com/sirupsen/logrus - misspell: - locale: US - lll: - line-length: 140 - goimports: - local-prefixes: github.com/golangci/golangci-lint - gocritic: - enabled-tags: - - performance - - style - - experimental - - diagnostic - disabled-checks: - - commentFormatting - - commentedOutCode - - evalOrder - - hugeParam - - octalLiteral - - rangeValCopy - - tooManyResultsChecker - - unnamedResult - -linters: - disable-all: true - enable: - - gocritic - - gofmt - - gosimple - - govet - - ineffassign - - misspell - - revive - - staticcheck - - unused - -run: - skip-dirs: - - pkg - -issues: - exclude: - - can't lint - - declaration of "err" shadows declaration at line - - should have a package comment, unless it's in another file for this package - - error strings should not be capitalized or end with punctuation or a newline - - Wrapf call needs 1 arg but has 2 args - - cs.NegotiatedProtocolIsMutual is deprecated diff --git a/.goreleaser.yml b/.goreleaser.yml index 7d57e657..9b5398a9 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -26,49 +26,7 @@ builds: flags: - -trimpath main: ./cmd/step-ca/main.go - binary: bin/step-ca - ldflags: - - -w -X main.Version={{.Version}} -X main.BuildTime={{.Date}} - - - id: step-cloudkms-init - env: - - CGO_ENABLED=0 - targets: - - darwin_amd64 - - darwin_arm64 - - freebsd_amd64 - - linux_386 - - linux_amd64 - - linux_arm64 - - linux_arm_5 - - linux_arm_6 - - linux_arm_7 - - windows_amd64 - flags: - - -trimpath - main: ./cmd/step-cloudkms-init/main.go - binary: bin/step-cloudkms-init - ldflags: - - -w -X main.Version={{.Version}} -X main.BuildTime={{.Date}} - - - id: step-awskms-init - env: - - CGO_ENABLED=0 - targets: - - darwin_amd64 - - darwin_arm64 - - freebsd_amd64 - - linux_386 - - linux_amd64 - - linux_arm64 - - linux_arm_5 - - linux_arm_6 - - linux_arm_7 - - windows_amd64 - flags: - - -trimpath - main: ./cmd/step-awskms-init/main.go - binary: bin/step-awskms-init + binary: step-ca ldflags: - -w -X main.Version={{.Version}} -X main.BuildTime={{.Date}} @@ -85,6 +43,38 @@ archives: files: - README.md - LICENSE + allow_different_binary_count: true + +nfpms: + # Configure nFPM for .deb and .rpm releases + # + # See https://nfpm.goreleaser.com/configuration/ + # and https://goreleaser.com/customization/nfpm/ + # + # Useful tools for debugging .debs: + # List file contents: dpkg -c dist/step_...deb + # Package metadata: dpkg --info dist/step_....deb + # + - + builds: + - step-ca + package_name: step-ca + file_name_template: "{{ .PackageName }}_{{ .Version }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" + vendor: Smallstep Labs + homepage: https://github.com/smallstep/certificates + maintainer: Smallstep + description: > + step-ca is an online certificate authority for secure, automated certificate management. + license: Apache 2.0 + section: utils + formats: + - deb + - rpm + priority: optional + bindir: /usr/bin + contents: + - src: debian/copyright + dst: /usr/share/doc/step-ca/copyright source: enabled: true @@ -97,8 +87,9 @@ checksum: signs: - cmd: cosign - stdin: '{{ .Env.COSIGN_PWD }}' - args: ["sign-blob", "-key=/tmp/cosign.key", "-output=${signature}", "${artifact}"] + signature: "${artifact}.sig" + certificate: "${artifact}.pem" + args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}"] artifacts: all snapshot: @@ -140,7 +131,7 @@ release: #### Linux - 📦 [step-ca_linux_{{ .Version }}_amd64.tar.gz](https://dl.step.sm/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_linux_{{ .Version }}_amd64.tar.gz) - - 📦 [step-ca_{{ .Env.DEB_VERSION }}_amd64.deb](https://dl.step.sm/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_{{ .Env.DEB_VERSION }}_amd64.deb) + - 📦 [step-ca_{{ .Version }}_amd64.deb](https://dl.step.sm/gh-release/certificates/gh-release-header/{{ .Tag }}/step-ca_{{ .Version }}_amd64.deb) #### OSX Darwin @@ -163,9 +154,9 @@ release: Below is an example using `cosign` to verify a release artifact: ``` - cosign verify-blob \ - -key https://raw.githubusercontent.com/smallstep/certificates/master/cosign.pub \ - -signature ~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz.sig + COSIGN_EXPERIMENTAL=1 cosign verify-blob \ + --certificate ~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz.sig.pem \ + --signature ~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz.sig \ ~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz ``` @@ -194,39 +185,3 @@ release: # - glob: ./path/to/file.txt # - glob: ./glob/**/to/**/file/**/* # - glob: ./glob/foo/to/bar/file/foobar/override_from_previous - -scoop: - # Template for the url which is determined by the given Token (github or gitlab) - # Default for github is "https://github.com///releases/download/{{ .Tag }}/{{ .ArtifactName }}" - # Default for gitlab is "https://gitlab.com///uploads/{{ .ArtifactUploadHash }}/{{ .ArtifactName }}" - # Default for gitea is "https://gitea.com///releases/download/{{ .Tag }}/{{ .ArtifactName }}" - url_template: "http://github.com/smallstep/certificates/releases/download/{{ .Tag }}/{{ .ArtifactName }}" - - # Repository to push the app manifest to. - bucket: - owner: smallstep - name: scoop-bucket - - # Git author used to commit to the repository. - # Defaults are shown. - commit_author: - name: goreleaserbot - email: goreleaser@smallstep.com - - # The project name and current git tag are used in the format string. - commit_msg_template: "Scoop update for {{ .ProjectName }} version {{ .Tag }}" - - # Your app's homepage. - # Default is empty. - homepage: "https://smallstep.com/docs/step-ca" - - # Skip uploads for prerelease. - skip_upload: auto - - # Your app's description. - # Default is empty. - description: "A private certificate authority (X.509 & SSH) & ACME server for secure automated certificate management, so you can use TLS everywhere & SSO for SSH." - - # Your app's license - # Default is empty. - license: "Apache-2.0" diff --git a/.version.sh b/.version.sh index 14adccbf..e7f823cd 100755 --- a/.version.sh +++ b/.version.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh read -r firstline < .VERSION last_half="${firstline##*tag: }" if [[ ${last_half::1} == "v" ]]; then diff --git a/CHANGELOG.md b/CHANGELOG.md index c0071b52..cb552de6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added - Added support for ACME device-attest-01 challenge. +- Added name constraints evaluation and enforcement when issuing or renewing + X.509 certificates. +- Added provisioner webhooks for augmenting template data and authorizing certificate requests before signing. +- Added automatic migration of provisioners when enabling remote managment. +- Added experimental support for CRLs. + +### Fixed +- MySQL DSN parsing issues fixed with upgrade to [smallstep/nosql@v0.5.0](https://github.com/smallstep/nosql/releases/tag/v0.5.0). ## [0.22.1] - 2022-08-31 ### Fixed diff --git a/Makefile b/Makefile index 906569f1..90e96993 100644 --- a/Makefile +++ b/Makefile @@ -28,8 +28,9 @@ ci: testcgo build ######################################### bootstra%: - # Using a released version of golangci-lint to take into account custom replacements in their go.mod - $Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.42.0 + $Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $$(go env GOPATH)/bin latest + $Q go install golang.org/x/vuln/cmd/govulncheck@latest + $Q go install gotest.tools/gotestsum@latest .PHONY: bootstra% @@ -78,8 +79,6 @@ $(info DEB_VERSION is $(DEB_VERSION)) $(info PUSHTYPE is $(PUSHTYPE)) endif -include make/docker.mk - ######################################### # Build ######################################### @@ -132,17 +131,18 @@ generate: # Test ######################################### test: - $Q $(GOFLAGS) go test -short -coverprofile=coverage.out ./... + $Q $(GOFLAGS) gotestsum -- -coverprofile=coverage.out -short -covermode=atomic ./... + testcgo: - $Q go test -short -coverprofile=coverage.out ./... + $Q gotestsum -- -coverprofile=coverage.out -short -covermode=atomic ./... .PHONY: test testcgo integrate: integration integration: bin/$(BINNAME) - $Q $(GOFLAGS) go test -tags=integration ./integration/... + $Q $(GOFLAGS) gotestsum -- -tags=integration ./integration/... .PHONY: integrate integration @@ -151,15 +151,14 @@ integration: bin/$(BINNAME) ######################################### fmt: - $Q gofmt -l -s -w $(SRC) + $Q goimports -l -w $(SRC) +lint: SHELL:=/bin/bash lint: - $Q golangci-lint run --timeout=30m + $Q LOG_LEVEL=error golangci-lint run --config <(curl -s https://raw.githubusercontent.com/smallstep/workflows/master/.golangci.yml) --timeout=30m + $Q govulncheck ./... -lintcgo: - $Q LOG_LEVEL=error golangci-lint run --timeout=30m - -.PHONY: fmt lint lintcgo +.PHONY: fmt lint ######################################### # Install @@ -231,11 +230,3 @@ debian: changelog distclean: clean .PHONY: changelog debian distclean - -################################################# -# Targets for creating step artifacts -################################################# - -docker-artifacts: docker-$(PUSHTYPE) - -.PHONY: docker-artifacts diff --git a/acme/account.go b/acme/account.go index 2dd412db..fa4b1167 100644 --- a/acme/account.go +++ b/acme/account.go @@ -33,7 +33,7 @@ func (a *Account) ToLog() (interface{}, error) { // IsValid returns true if the Account is valid. func (a *Account) IsValid() bool { - return Status(a.Status) == StatusValid + return a.Status == StatusValid } // KeyToID converts a JWK to a thumbprint. diff --git a/acme/account_test.go b/acme/account_test.go index edd1f5b0..88718a9a 100644 --- a/acme/account_test.go +++ b/acme/account_test.go @@ -46,14 +46,14 @@ func TestKeyToID(t *testing.T) { tc := run(t) if id, err := KeyToID(tc.jwk); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } @@ -131,12 +131,13 @@ func TestExternalAccountKey_BindTo(t *testing.T) { } if wantErr { assert.NotNil(t, err) - assert.Type(t, &Error{}, err) - ae, _ := err.(*Error) - assert.Equals(t, ae.Type, tt.err.Type) - assert.Equals(t, ae.Detail, tt.err.Detail) - assert.Equals(t, ae.Identifier, tt.err.Identifier) - assert.Equals(t, ae.Subproblems, tt.err.Subproblems) + var ae *Error + if assert.True(t, errors.As(err, &ae)) { + assert.Equals(t, ae.Type, tt.err.Type) + assert.Equals(t, ae.Detail, tt.err.Detail) + assert.Equals(t, ae.Identifier, tt.err.Identifier) + assert.Equals(t, ae.Subproblems, tt.err.Subproblems) + } } else { assert.Equals(t, eak.AccountID, acct.ID) assert.Equals(t, eak.HmacKey, []byte{}) diff --git a/acme/api/account.go b/acme/api/account.go index 710747ca..954cb9de 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -2,6 +2,7 @@ package api import ( "encoding/json" + "errors" "net/http" "github.com/go-chi/chi" @@ -97,8 +98,8 @@ func NewAccount(w http.ResponseWriter, r *http.Request) { httpStatus := http.StatusCreated acc, err := accountFromContext(ctx) if err != nil { - acmeErr, ok := err.(*acme.Error) - if !ok || acmeErr.Status != http.StatusBadRequest { + var acmeErr *acme.Error + if !errors.As(err, &acmeErr) || acmeErr.Status != http.StatusBadRequest { // Something went wrong ... render.Error(w, err) return diff --git a/acme/api/account_test.go b/acme/api/account_test.go index d74b5433..3f8641b8 100644 --- a/acme/api/account_test.go +++ b/acme/api/account_test.go @@ -197,11 +197,12 @@ func TestNewAccountRequest_Validate(t *testing.T) { t.Run(name, func(t *testing.T) { if err := tc.nar.Validate(); err != nil { if assert.NotNil(t, err) { - ae, ok := err.(*acme.Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) + var ae *acme.Error + if assert.True(t, errors.As(err, &ae)) { + assert.HasPrefix(t, ae.Error(), tc.err.Error()) + assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) + assert.Equals(t, ae.Type, tc.err.Type) + } } } else { assert.Nil(t, tc.err) @@ -268,11 +269,12 @@ func TestUpdateAccountRequest_Validate(t *testing.T) { t.Run(name, func(t *testing.T) { if err := tc.uar.Validate(); err != nil { if assert.NotNil(t, err) { - ae, ok := err.(*acme.Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) + var ae *acme.Error + if assert.True(t, errors.As(err, &ae)) { + assert.HasPrefix(t, ae.Error(), tc.err.Error()) + assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) + assert.Equals(t, ae.Type, tc.err.Type) + } } } else { assert.Nil(t, tc.err) diff --git a/acme/api/eab.go b/acme/api/eab.go index 4c4fff04..26854595 100644 --- a/acme/api/eab.go +++ b/acme/api/eab.go @@ -3,6 +3,7 @@ package api import ( "context" "encoding/json" + "errors" "go.step.sm/crypto/jose" @@ -24,6 +25,7 @@ func validateExternalAccountBinding(ctx context.Context, nar *NewAccountRequest) } if !acmeProv.RequireEAB { + //nolint:nilnil // legacy return nil, nil } @@ -51,7 +53,8 @@ func validateExternalAccountBinding(ctx context.Context, nar *NewAccountRequest) db := acme.MustDatabaseFromContext(ctx) externalAccountKey, err := db.GetExternalAccountKey(ctx, acmeProv.ID, keyID) if err != nil { - if _, ok := err.(*acme.Error); ok { + var ae *acme.Error + if errors.As(err, &ae) { return nil, acme.WrapError(acme.ErrorUnauthorizedType, err, "the field 'kid' references an unknown key") } return nil, acme.WrapErrorISE(err, "error retrieving external account key") diff --git a/acme/api/eab_test.go b/acme/api/eab_test.go index d2e596f9..c923a2f6 100644 --- a/acme/api/eab_test.go +++ b/acme/api/eab_test.go @@ -860,13 +860,15 @@ func TestHandler_validateExternalAccountBinding(t *testing.T) { if wantErr { assert.NotNil(t, err) assert.Type(t, &acme.Error{}, err) - ae, _ := err.(*acme.Error) - assert.Equals(t, ae.Type, tc.err.Type) - assert.Equals(t, ae.Status, tc.err.Status) - assert.HasPrefix(t, ae.Err.Error(), tc.err.Err.Error()) - assert.Equals(t, ae.Detail, tc.err.Detail) - assert.Equals(t, ae.Identifier, tc.err.Identifier) - assert.Equals(t, ae.Subproblems, tc.err.Subproblems) + var ae *acme.Error + if assert.True(t, errors.As(err, &ae)) { + assert.Equals(t, ae.Type, tc.err.Type) + assert.Equals(t, ae.Status, tc.err.Status) + assert.HasPrefix(t, ae.Err.Error(), tc.err.Err.Error()) + assert.Equals(t, ae.Detail, tc.err.Detail) + assert.Equals(t, ae.Identifier, tc.err.Identifier) + assert.Equals(t, ae.Subproblems, tc.err.Subproblems) + } } else { if got == nil { assert.Nil(t, tc.eak) diff --git a/acme/api/handler.go b/acme/api/handler.go index 6ae57ab8..776f012b 100644 --- a/acme/api/handler.go +++ b/acme/api/handler.go @@ -205,7 +205,7 @@ type Directory struct { NewOrder string `json:"newOrder"` RevokeCert string `json:"revokeCert"` KeyChange string `json:"keyChange"` - Meta Meta `json:"meta"` + Meta *Meta `json:"meta,omitempty"` } // ToLog enables response logging for the Directory type. @@ -228,18 +228,49 @@ func GetDirectory(w http.ResponseWriter, r *http.Request) { } linker := acme.MustLinkerFromContext(ctx) + render.JSON(w, &Directory{ NewNonce: linker.GetLink(ctx, acme.NewNonceLinkType), NewAccount: linker.GetLink(ctx, acme.NewAccountLinkType), NewOrder: linker.GetLink(ctx, acme.NewOrderLinkType), RevokeCert: linker.GetLink(ctx, acme.RevokeCertLinkType), KeyChange: linker.GetLink(ctx, acme.KeyChangeLinkType), - Meta: Meta{ - ExternalAccountRequired: acmeProv.RequireEAB, - }, + Meta: createMetaObject(acmeProv), }) } +// createMetaObject creates a Meta object if the ACME provisioner +// has one or more properties that are written in the ACME directory output. +// It returns nil if none of the properties are set. +func createMetaObject(p *provisioner.ACME) *Meta { + if shouldAddMetaObject(p) { + return &Meta{ + TermsOfService: p.TermsOfService, + Website: p.Website, + CaaIdentities: p.CaaIdentities, + ExternalAccountRequired: p.RequireEAB, + } + } + return nil +} + +// shouldAddMetaObject returns whether or not the ACME provisioner +// has properties configured that must be added to the ACME directory object. +func shouldAddMetaObject(p *provisioner.ACME) bool { + switch { + case p.TermsOfService != "": + return true + case p.Website != "": + return true + case len(p.CaaIdentities) > 0: + return true + case p.RequireEAB: + return true + default: + return false + } +} + // NotImplemented returns a 501 and is generally a placeholder for functionality which // MAY be added at some point in the future but is not in any way a guarantee of such. func NotImplemented(w http.ResponseWriter, r *http.Request) { diff --git a/acme/api/handler_test.go b/acme/api/handler_test.go index 822409df..1edeb501 100644 --- a/acme/api/handler_test.go +++ b/acme/api/handler_test.go @@ -18,10 +18,13 @@ import ( "github.com/go-chi/chi" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" - "github.com/smallstep/assert" - "github.com/smallstep/certificates/acme" + "go.step.sm/crypto/jose" "go.step.sm/crypto/pemutil" + + "github.com/smallstep/assert" + "github.com/smallstep/certificates/acme" + "github.com/smallstep/certificates/authority/provisioner" ) type mockClient struct { @@ -129,7 +132,35 @@ func TestHandler_GetDirectory(t *testing.T) { NewOrder: fmt.Sprintf("%s/acme/%s/new-order", baseURL.String(), provName), RevokeCert: fmt.Sprintf("%s/acme/%s/revoke-cert", baseURL.String(), provName), KeyChange: fmt.Sprintf("%s/acme/%s/key-change", baseURL.String(), provName), - Meta: Meta{ + Meta: &Meta{ + ExternalAccountRequired: true, + }, + } + return test{ + ctx: ctx, + dir: expDir, + statusCode: 200, + } + }, + "ok/full-meta": func(t *testing.T) test { + prov := newACMEProv(t) + prov.TermsOfService = "https://terms.ca.local/" + prov.Website = "https://ca.local/" + prov.CaaIdentities = []string{"ca.local"} + prov.RequireEAB = true + provName := url.PathEscape(prov.GetName()) + baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"} + ctx := acme.NewProvisionerContext(context.Background(), prov) + expDir := Directory{ + NewNonce: fmt.Sprintf("%s/acme/%s/new-nonce", baseURL.String(), provName), + NewAccount: fmt.Sprintf("%s/acme/%s/new-account", baseURL.String(), provName), + NewOrder: fmt.Sprintf("%s/acme/%s/new-order", baseURL.String(), provName), + RevokeCert: fmt.Sprintf("%s/acme/%s/revoke-cert", baseURL.String(), provName), + KeyChange: fmt.Sprintf("%s/acme/%s/key-change", baseURL.String(), provName), + Meta: &Meta{ + TermsOfService: "https://terms.ca.local/", + Website: "https://ca.local/", + CaaIdentities: []string{"ca.local"}, ExternalAccountRequired: true, }, } @@ -751,3 +782,89 @@ func TestHandler_GetChallenge(t *testing.T) { }) } } + +func Test_createMetaObject(t *testing.T) { + tests := []struct { + name string + p *provisioner.ACME + want *Meta + }{ + { + name: "no-meta", + p: &provisioner.ACME{ + Type: "ACME", + Name: "acme", + }, + want: nil, + }, + { + name: "terms-of-service", + p: &provisioner.ACME{ + Type: "ACME", + Name: "acme", + TermsOfService: "https://terms.ca.local", + }, + want: &Meta{ + TermsOfService: "https://terms.ca.local", + }, + }, + { + name: "website", + p: &provisioner.ACME{ + Type: "ACME", + Name: "acme", + Website: "https://ca.local", + }, + want: &Meta{ + Website: "https://ca.local", + }, + }, + { + name: "caa", + p: &provisioner.ACME{ + Type: "ACME", + Name: "acme", + CaaIdentities: []string{"ca.local", "ca.remote"}, + }, + want: &Meta{ + CaaIdentities: []string{"ca.local", "ca.remote"}, + }, + }, + { + name: "require-eab", + p: &provisioner.ACME{ + Type: "ACME", + Name: "acme", + RequireEAB: true, + }, + want: &Meta{ + ExternalAccountRequired: true, + }, + }, + { + name: "full-meta", + p: &provisioner.ACME{ + Type: "ACME", + Name: "acme", + TermsOfService: "https://terms.ca.local", + Website: "https://ca.local", + CaaIdentities: []string{"ca.local", "ca.remote"}, + RequireEAB: true, + }, + want: &Meta{ + TermsOfService: "https://terms.ca.local", + Website: "https://ca.local", + CaaIdentities: []string{"ca.local", "ca.remote"}, + ExternalAccountRequired: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := createMetaObject(tt.p) + if !cmp.Equal(tt.want, got) { + t.Errorf("createMetaObject() diff =\n%s", cmp.Diff(tt.want, got)) + } + }) + } +} diff --git a/acme/api/middleware_test.go b/acme/api/middleware_test.go index e43f6f99..faff0616 100644 --- a/acme/api/middleware_test.go +++ b/acme/api/middleware_test.go @@ -518,9 +518,6 @@ func TestHandler_verifyAndExtractJWSPayload(t *testing.T) { } }, "ok/empty-algorithm-in-jwk": func(t *testing.T) test { - _pub := *pub - clone := &_pub - clone.Algorithm = "" ctx := context.WithValue(context.Background(), jwsContextKey, parsedJWS) ctx = context.WithValue(ctx, jwkContextKey, pub) return test{ diff --git a/acme/api/order_test.go b/acme/api/order_test.go index b83dfc7c..b7b58b7f 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -179,11 +179,12 @@ func TestNewOrderRequest_Validate(t *testing.T) { t.Run(name, func(t *testing.T) { if err := tc.nor.Validate(); err != nil { if assert.NotNil(t, err) { - ae, ok := err.(*acme.Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) + var ae *acme.Error + if assert.True(t, errors.As(err, &ae)) { + assert.HasPrefix(t, ae.Error(), tc.err.Error()) + assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) + assert.Equals(t, ae.Type, tc.err.Type) + } } } else { if assert.Nil(t, tc.err) { @@ -253,11 +254,12 @@ func TestFinalizeRequestValidate(t *testing.T) { t.Run(name, func(t *testing.T) { if err := tc.fr.Validate(); err != nil { if assert.NotNil(t, err) { - ae, ok := err.(*acme.Error) - assert.True(t, ok) - assert.HasPrefix(t, ae.Error(), tc.err.Error()) - assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) - assert.Equals(t, ae.Type, tc.err.Type) + var ae *acme.Error + if assert.True(t, errors.As(err, &ae)) { + assert.HasPrefix(t, ae.Error(), tc.err.Error()) + assert.Equals(t, ae.StatusCode(), tc.err.StatusCode()) + assert.Equals(t, ae.Type, tc.err.Type) + } } } else { if assert.Nil(t, tc.err) { @@ -756,19 +758,22 @@ func TestHandler_newAuthorization(t *testing.T) { } for name, run := range tests { t.Run(name, func(t *testing.T) { + if name == "ok/permanent-identifier-enabled" { + println(1) + } tc := run(t) ctx := newBaseContext(context.Background(), tc.db) ctx = acme.NewProvisionerContext(ctx, tc.prov) if err := newAuthorization(ctx, tc.az); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *acme.Error: + var k *acme.Error + if assert.True(t, errors.As(err, &k)) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } diff --git a/acme/authorization_test.go b/acme/authorization_test.go index 00b35b99..28aefe9f 100644 --- a/acme/authorization_test.go +++ b/acme/authorization_test.go @@ -130,14 +130,14 @@ func TestAuthorization_UpdateStatus(t *testing.T) { tc := run(t) if err := tc.az.UpdateStatus(context.Background(), tc.db); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } diff --git a/acme/challenge.go b/acme/challenge.go index 64cb871d..baf5572a 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -48,6 +48,18 @@ const ( DEVICEATTEST01 ChallengeType = "device-attest-01" ) +var ( + // InsecurePortHTTP01 is the port used to verify http-01 challenges. If not set it + // defaults to 80. + InsecurePortHTTP01 int + + // InsecurePortTLSALPN01 is the port used to verify tls-alpn-01 challenges. If not + // set it defaults to 443. + // + // This variable can be used for testing purposes. + InsecurePortTLSALPN01 int +) + // Challenge represents an ACME response Challenge type. type Challenge struct { ID string `json:"-"` @@ -97,6 +109,12 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey, func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey) error { u := &url.URL{Scheme: "http", Host: http01ChallengeHost(ch.Value), Path: fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Token)} + // Append insecure port if set. + // Only used for testing purposes. + if InsecurePortHTTP01 != 0 { + u.Host += ":" + strconv.Itoa(InsecurePortHTTP01) + } + vc := MustClientFromContext(ctx) resp, err := vc.Get(u.String()) if err != nil { @@ -166,10 +184,17 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON // [RFC5246] or higher when connecting to clients for validation. MinVersion: tls.VersionTLS12, ServerName: serverName(ch), - InsecureSkipVerify: true, // nolint:gosec // we expect a self-signed challenge certificate + InsecureSkipVerify: true, //nolint:gosec // we expect a self-signed challenge certificate } - hostPort := net.JoinHostPort(ch.Value, "443") + var hostPort string + + // Allow to change TLS port for testing purposes. + if port := InsecurePortTLSALPN01; port == 0 { + hostPort = net.JoinHostPort(ch.Value, "443") + } else { + hostPort = net.JoinHostPort(ch.Value, strconv.Itoa(port)) + } vc := MustClientFromContext(ctx) conn, err := vc.TLSDial("tcp", hostPort, config) diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 90aafa97..1aa9f6ab 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -24,6 +24,7 @@ import ( "net/http" "net/http/httptest" "reflect" + "strconv" "strings" "testing" "time" @@ -188,14 +189,14 @@ func Test_storeError(t *testing.T) { tc := run(t) if err := storeError(context.Background(), tc.db, tc.ch, tc.markInvalid, err); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } @@ -243,14 +244,14 @@ func TestKeyAuthorization(t *testing.T) { tc := run(t) if ka, err := KeyAuthorization(tc.token, tc.jwk); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } @@ -370,6 +371,47 @@ func TestChallenge_Validate(t *testing.T) { }, } }, + "ok/http-01-insecure": func(t *testing.T) test { + t.Cleanup(func() { + InsecurePortHTTP01 = 0 + }) + + ch := &Challenge{ + ID: "chID", + Status: StatusPending, + Type: "http-01", + Token: "token", + Value: "zap.internal", + } + + InsecurePortHTTP01 = 8080 + + return test{ + ch: ch, + vc: &mockClient{ + get: func(url string) (*http.Response, error) { + return nil, errors.New("force") + }, + }, + db: &MockDB{ + MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { + assert.Equals(t, updch.ID, ch.ID) + assert.Equals(t, updch.Token, ch.Token) + assert.Equals(t, updch.Type, ch.Type) + assert.Equals(t, updch.Status, ch.Status) + assert.Equals(t, updch.Value, ch.Value) + + err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal:8080/.well-known/acme-challenge/%s: force", ch.Token) + assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error()) + assert.Equals(t, updch.Error.Type, err.Type) + assert.Equals(t, updch.Error.Detail, err.Detail) + assert.Equals(t, updch.Error.Status, err.Status) + assert.Equals(t, updch.Error.Detail, err.Detail) + return nil + }, + }, + } + }, "fail/dns-01": func(t *testing.T) test { ch := &Challenge{ ID: "chID", @@ -501,6 +543,72 @@ func TestChallenge_Validate(t *testing.T) { srv, tlsDial := newTestTLSALPNServer(cert) srv.Start() + return test{ + ch: ch, + vc: &mockClient{ + tlsDial: tlsDial, + }, + db: &MockDB{ + MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { + assert.Equals(t, updch.ID, ch.ID) + assert.Equals(t, updch.Token, ch.Token) + assert.Equals(t, updch.Status, ch.Status) + assert.Equals(t, updch.Type, ch.Type) + assert.Equals(t, updch.Value, ch.Value) + assert.Equals(t, updch.Error, nil) + return nil + }, + }, + srv: srv, + jwk: jwk, + } + }, + "ok/tls-alpn-01-insecure": func(t *testing.T) test { + t.Cleanup(func() { + InsecurePortTLSALPN01 = 0 + }) + + ch := &Challenge{ + ID: "chID", + Token: "token", + Type: "tls-alpn-01", + Status: StatusPending, + Value: "zap.internal", + } + + jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + assert.FatalError(t, err) + + expKeyAuth, err := KeyAuthorization(ch.Token, jwk) + assert.FatalError(t, err) + expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth)) + + cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], false, true, ch.Value) + assert.FatalError(t, err) + + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + if l, err = net.Listen("tcp6", "[::1]:0"); err != nil { + t.Fatalf("failed to listen on a port: %v", err) + } + } + _, port, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + t.Fatalf("failed to split host port: %v", err) + } + + // Use an insecure port + InsecurePortTLSALPN01, err = strconv.Atoi(port) + if err != nil { + t.Fatalf("failed to convert port to int: %v", err) + } + + srv, tlsDial := newTestTLSALPNServer(cert, func(srv *httptest.Server) { + srv.Listener.Close() + srv.Listener = l + }) + srv.Start() + return test{ ch: ch, vc: &mockClient{ @@ -533,14 +641,14 @@ func TestChallenge_Validate(t *testing.T) { ctx := NewClientContext(context.Background(), tc.vc) if err := tc.ch.Validate(ctx, tc.db, tc.jwk, nil); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } @@ -928,14 +1036,14 @@ func TestHTTP01Validate(t *testing.T) { ctx := NewClientContext(context.Background(), tc.vc) if err := http01Validate(ctx, tc.ch, tc.db, tc.jwk); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } @@ -1228,14 +1336,14 @@ func TestDNS01Validate(t *testing.T) { ctx := NewClientContext(context.Background(), tc.vc) if err := dns01Validate(ctx, tc.ch, tc.db, tc.jwk); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } @@ -1248,7 +1356,7 @@ func TestDNS01Validate(t *testing.T) { type tlsDialer func(network, addr string, config *tls.Config) (conn *tls.Conn, err error) -func newTestTLSALPNServer(validationCert *tls.Certificate) (*httptest.Server, tlsDialer) { +func newTestTLSALPNServer(validationCert *tls.Certificate, opts ...func(*httptest.Server)) (*httptest.Server, tlsDialer) { srv := httptest.NewUnstartedServer(http.NewServeMux()) srv.Config.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){ @@ -1273,6 +1381,11 @@ func newTestTLSALPNServer(validationCert *tls.Certificate) (*httptest.Server, tl }, } + // Apply options + for _, fn := range opts { + fn(srv) + } + srv.Listener = tls.NewListener(srv.Listener, srv.TLS) //srv.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // hush @@ -2298,14 +2411,14 @@ func TestTLSALPN01Validate(t *testing.T) { ctx := NewClientContext(context.Background(), tc.vc) if err := tlsalpn01Validate(ctx, tc.ch, tc.db, tc.jwk); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } @@ -2774,3 +2887,97 @@ func Test_doStepAttestationFormat(t *testing.T) { }) } } + +func Test_doStepAttestationFormat_noCAIntermediate(t *testing.T) { + ctx := context.Background() + + // This CA simulates a YubiKey v5.2.4, where the attestation intermediate in + // the CA does not have the basic constraint extension. With the current + // validation of the certificate the test case below returns an error. If + // we change the validation to support this use case, the test case below + // should change. + // + // See https://github.com/Yubico/yubikey-manager/issues/522 + ca, err := minica.New(minica.WithIntermediateTemplate(`{"subject": {{ toJson .Subject }}}`)) + if err != nil { + t.Fatal(err) + } + caRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Root.Raw}) + + makeLeaf := func(signer crypto.Signer, serialNumber []byte) *x509.Certificate { + leaf, err := ca.Sign(&x509.Certificate{ + Subject: pkix.Name{CommonName: "attestation cert"}, + PublicKey: signer.Public(), + ExtraExtensions: []pkix.Extension{ + {Id: oidYubicoSerialNumber, Value: serialNumber}, + }, + }) + if err != nil { + t.Fatal(err) + } + return leaf + } + + signer, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + serialNumber, err := asn1.Marshal(1234) + if err != nil { + t.Fatal(err) + } + leaf := makeLeaf(signer, serialNumber) + + jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + if err != nil { + t.Fatal(err) + } + keyAuth, err := KeyAuthorization("token", jwk) + if err != nil { + t.Fatal(err) + } + keyAuthSum := sha256.Sum256([]byte(keyAuth)) + sig, err := signer.Sign(rand.Reader, keyAuthSum[:], crypto.SHA256) + if err != nil { + t.Fatal(err) + } + cborSig, err := cbor.Marshal(sig) + if err != nil { + t.Fatal(err) + } + + type args struct { + ctx context.Context + prov Provisioner + ch *Challenge + jwk *jose.JSONWebKey + att *AttestationObject + } + tests := []struct { + name string + args args + want *stepAttestationData + wantErr bool + }{ + {"fail no intermediate", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &AttestationObject{ + Format: "step", + AttStatement: map[string]interface{}{ + "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + "alg": -7, + "sig": cborSig, + }, + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := doStepAttestationFormat(tt.args.ctx, tt.args.prov, tt.args.ch, tt.args.jwk, tt.args.att) + if (err != nil) != tt.wantErr { + t.Errorf("doStepAttestationFormat() error = %#v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("doStepAttestationFormat() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/acme/client.go b/acme/client.go index cf5f8c09..51560cb8 100644 --- a/acme/client.go +++ b/acme/client.go @@ -56,7 +56,7 @@ func NewClient() Client { Timeout: 30 * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{ - // nolint:gosec // used on tls-alpn-01 challenge + //nolint:gosec // used on tls-alpn-01 challenge InsecureSkipVerify: true, // lgtm[go/disabled-certificate-check] }, }, diff --git a/acme/db/nosql/account_test.go b/acme/db/nosql/account_test.go index 83a23476..6097cc5a 100644 --- a/acme/db/nosql/account_test.go +++ b/acme/db/nosql/account_test.go @@ -95,16 +95,16 @@ func TestDB_getDBAccount(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if dbacc, err := d.getDBAccount(context.Background(), accID); err != nil { - switch k := err.(type) { - case *acme.Error: + var acmeErr *acme.Error + if errors.As(err, &acmeErr) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Type, tc.acmeErr.Type) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Status, tc.acmeErr.Status) + assert.Equals(t, acmeErr.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -174,16 +174,16 @@ func TestDB_getAccountIDByKeyID(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if retAccID, err := d.getAccountIDByKeyID(context.Background(), kid); err != nil { - switch k := err.(type) { - case *acme.Error: + var acmeErr *acme.Error + if errors.As(err, &acmeErr) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Type, tc.acmeErr.Type) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Status, tc.acmeErr.Status) + assert.Equals(t, acmeErr.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -248,16 +248,16 @@ func TestDB_GetAccount(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if acc, err := d.GetAccount(context.Background(), accID); err != nil { - switch k := err.(type) { - case *acme.Error: + var acmeErr *acme.Error + if errors.As(err, &acmeErr) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Type, tc.acmeErr.Type) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Status, tc.acmeErr.Status) + assert.Equals(t, acmeErr.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -354,16 +354,16 @@ func TestDB_GetAccountByKeyID(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if acc, err := d.GetAccountByKeyID(context.Background(), kid); err != nil { - switch k := err.(type) { - case *acme.Error: + var acmeErr *acme.Error + if errors.As(err, &acmeErr) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Type, tc.acmeErr.Type) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Status, tc.acmeErr.Status) + assert.Equals(t, acmeErr.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/acme/db/nosql/authz_test.go b/acme/db/nosql/authz_test.go index c41fabb5..c7d47eda 100644 --- a/acme/db/nosql/authz_test.go +++ b/acme/db/nosql/authz_test.go @@ -101,16 +101,16 @@ func TestDB_getDBAuthz(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if dbaz, err := d.getDBAuthz(context.Background(), azID); err != nil { - switch k := err.(type) { - case *acme.Error: + var acmeErr *acme.Error + if errors.As(err, &acmeErr) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Type, tc.acmeErr.Type) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Status, tc.acmeErr.Status) + assert.Equals(t, acmeErr.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -295,16 +295,16 @@ func TestDB_GetAuthorization(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if az, err := d.GetAuthorization(context.Background(), azID); err != nil { - switch k := err.(type) { - case *acme.Error: + var acmeErr *acme.Error + if errors.As(err, &acmeErr) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Type, tc.acmeErr.Type) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Status, tc.acmeErr.Status) + assert.Equals(t, acmeErr.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -745,16 +745,16 @@ func TestDB_GetAuthorizationsByAccountID(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if azs, err := d.GetAuthorizationsByAccountID(context.Background(), accountID); err != nil { - switch k := err.(type) { - case *acme.Error: + var acmeErr *acme.Error + if errors.As(err, &acmeErr) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Type, tc.acmeErr.Type) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Status, tc.acmeErr.Status) + assert.Equals(t, acmeErr.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/acme/db/nosql/certificate.go b/acme/db/nosql/certificate.go index ee37c570..8f271ba5 100644 --- a/acme/db/nosql/certificate.go +++ b/acme/db/nosql/certificate.go @@ -138,5 +138,4 @@ func parseBundle(b []byte) ([]*x509.Certificate, error) { return nil, errors.New("error decoding PEM: unexpected data") } return bundle, nil - } diff --git a/acme/db/nosql/certificate_test.go b/acme/db/nosql/certificate_test.go index d64b3015..ba16a175 100644 --- a/acme/db/nosql/certificate_test.go +++ b/acme/db/nosql/certificate_test.go @@ -250,16 +250,16 @@ func TestDB_GetCertificate(t *testing.T) { d := DB{db: tc.db} cert, err := d.GetCertificate(context.Background(), certID) if err != nil { - switch k := err.(type) { - case *acme.Error: + var acmeErr *acme.Error + if errors.As(err, &acmeErr) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Type, tc.acmeErr.Type) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) + assert.Equals(t, acmeErr.Status, tc.acmeErr.Status) + assert.Equals(t, acmeErr.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, acmeErr.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -444,16 +444,16 @@ func TestDB_GetCertificateBySerial(t *testing.T) { d := DB{db: tc.db} cert, err := d.GetCertificateBySerial(context.Background(), serial) if err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/acme/db/nosql/challenge_test.go b/acme/db/nosql/challenge_test.go index 08c5a608..4eb815f5 100644 --- a/acme/db/nosql/challenge_test.go +++ b/acme/db/nosql/challenge_test.go @@ -94,16 +94,16 @@ func TestDB_getDBChallenge(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if ch, err := d.getDBChallenge(context.Background(), chID); err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -286,16 +286,16 @@ func TestDB_GetChallenge(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if ch, err := d.GetChallenge(context.Background(), chID, azID); err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/acme/db/nosql/eab.go b/acme/db/nosql/eab.go index e87aa9bc..e3651151 100644 --- a/acme/db/nosql/eab.go +++ b/acme/db/nosql/eab.go @@ -54,7 +54,6 @@ func (db *DB) getDBExternalAccountKey(ctx context.Context, id string) (*dbExtern // CreateExternalAccountKey creates a new External Account Binding key with a name func (db *DB) CreateExternalAccountKey(ctx context.Context, provisionerID, reference string) (*acme.ExternalAccountKey, error) { - externalAccountKeyMutex.Lock() defer externalAccountKeyMutex.Unlock() @@ -210,6 +209,7 @@ func (db *DB) GetExternalAccountKeyByReference(ctx context.Context, provisionerI defer externalAccountKeyMutex.RUnlock() if reference == "" { + //nolint:nilnil // legacy return nil, nil } @@ -228,6 +228,7 @@ func (db *DB) GetExternalAccountKeyByReference(ctx context.Context, provisionerI } func (db *DB) GetExternalAccountKeyByAccountID(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) { + //nolint:nilnil // legacy return nil, nil } @@ -371,7 +372,6 @@ func sliceIndex(slice []string, item string) int { // removeElement deletes the item if it exists in the // slice. It returns a new slice, keeping the old one intact. func removeElement(slice []string, item string) []string { - newSlice := make([]string, 0) index := sliceIndex(slice, item) if index < 0 { diff --git a/acme/db/nosql/eab_test.go b/acme/db/nosql/eab_test.go index 525afa72..51097911 100644 --- a/acme/db/nosql/eab_test.go +++ b/acme/db/nosql/eab_test.go @@ -93,16 +93,16 @@ func TestDB_getDBExternalAccountKey(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if dbeak, err := d.getDBExternalAccountKey(context.Background(), keyID); err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -210,16 +210,16 @@ func TestDB_GetExternalAccountKey(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if eak, err := d.GetExternalAccountKey(context.Background(), provID, keyID); err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -374,16 +374,16 @@ func TestDB_GetExternalAccountKeyByReference(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if eak, err := d.GetExternalAccountKeyByReference(context.Background(), provID, tc.ref); err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -580,16 +580,16 @@ func TestDB_GetExternalAccountKeys(t *testing.T) { cursor, limit := "", 0 if eaks, nextCursor, err := d.GetExternalAccountKeys(context.Background(), provID, cursor, limit); err != nil { assert.Equals(t, "", nextCursor) - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.Equals(t, tc.err.Error(), err.Error()) } @@ -672,7 +672,7 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) { return errors.New("force default") } }, - MCmpAndSwap: func(bucket, key, old, new []byte) ([]byte, bool, error) { + MCmpAndSwap: func(bucket, key, old, nu []byte) ([]byte, bool, error) { fmt.Println(string(bucket)) switch string(bucket) { case string(externalAccountKeyIDsByReferenceTable): @@ -882,16 +882,16 @@ func TestDB_DeleteExternalAccountKey(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if err := d.DeleteExternalAccountKey(context.Background(), provID, keyID); err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.Equals(t, err.Error(), tc.err.Error()) } diff --git a/acme/db/nosql/nonce_test.go b/acme/db/nosql/nonce_test.go index 7dc5cc91..253731bf 100644 --- a/acme/db/nosql/nonce_test.go +++ b/acme/db/nosql/nonce_test.go @@ -146,16 +146,16 @@ func TestDB_DeleteNonce(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if err := d.DeleteNonce(context.Background(), acme.Nonce(nonceID)); err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/acme/db/nosql/order_test.go b/acme/db/nosql/order_test.go index ff9396bd..cf22f094 100644 --- a/acme/db/nosql/order_test.go +++ b/acme/db/nosql/order_test.go @@ -102,16 +102,16 @@ func TestDB_getDBOrder(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if dbo, err := d.getDBOrder(context.Background(), orderID); err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -206,16 +206,16 @@ func TestDB_GetOrder(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if o, err := d.GetOrder(context.Background(), orderID); err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -1003,16 +1003,16 @@ func TestDB_updateAddOrderIDs(t *testing.T) { } if err != nil { - switch k := err.(type) { - case *acme.Error: + var ae *acme.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.acmeErr) { - assert.Equals(t, k.Type, tc.acmeErr.Type) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) - assert.Equals(t, k.Status, tc.acmeErr.Status) - assert.Equals(t, k.Err.Error(), tc.acmeErr.Err.Error()) - assert.Equals(t, k.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Type, tc.acmeErr.Type) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) + assert.Equals(t, ae.Status, tc.acmeErr.Status) + assert.Equals(t, ae.Err.Error(), tc.acmeErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.acmeErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/acme/errors.go b/acme/errors.go index 34421500..a969bd96 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -75,6 +75,8 @@ func (ap ProblemType) String() string { return "accountDoesNotExist" case ErrorAlreadyRevokedType: return "alreadyRevoked" + case ErrorBadAttestationStatementType: + return "badAttestationStatement" case ErrorBadCSRType: return "badCSR" case ErrorBadNonceType: @@ -310,10 +312,11 @@ func NewErrorISE(msg string, args ...interface{}) *Error { // WrapError attempts to wrap the internal error. func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Error { - switch e := err.(type) { - case nil: + var e *Error + switch { + case err == nil: return nil - case *Error: + case errors.As(err, &e): if e.Err == nil { e.Err = errors.Errorf(msg+"; "+e.Detail, args...) } else { diff --git a/acme/order.go b/acme/order.go index 2eddad53..7748df22 100644 --- a/acme/order.go +++ b/acme/order.go @@ -194,6 +194,14 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques if err != nil { return WrapErrorISE(err, "error retrieving authorization options from ACME provisioner") } + // Unlike most of the provisioners, ACME's AuthorizeSign method doesn't + // define the templates, and the template data used in WebHooks is not + // available. + for _, signOp := range signOps { + if wc, ok := signOp.(*provisioner.WebhookController); ok { + wc.TemplateData = data + } + } templateOptions, err := provisioner.CustomTemplateOptions(p.GetOptions(), data, defaultTemplate) if err != nil { @@ -324,7 +332,6 @@ func numberOfIdentifierType(typ IdentifierType, ids []Identifier) int { // addresses or DNS names slice, depending on whether it can be parsed as an IP // or not. This might result in an additional SAN in the final certificate. func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.CertificateRequest) { - // for clarity only; we're operating on the same object by pointer canonicalized = csr diff --git a/acme/order_test.go b/acme/order_test.go index f1f28e40..606e9f71 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -247,14 +247,14 @@ func TestOrder_UpdateStatus(t *testing.T) { tc := run(t) if err := tc.o.UpdateStatus(context.Background(), tc.db); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } @@ -812,14 +812,14 @@ func TestOrder_Finalize(t *testing.T) { tc := run(t) if err := tc.o.Finalize(context.Background(), tc.db, tc.csr, tc.ca, tc.prov); err != nil { if assert.NotNil(t, tc.err) { - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tc.err.Type) assert.Equals(t, k.Detail, tc.err.Detail) assert.Equals(t, k.Status, tc.err.Status) assert.Equals(t, k.Err.Error(), tc.err.Err.Error()) assert.Equals(t, k.Detail, tc.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } } @@ -1474,14 +1474,14 @@ func TestOrder_sans(t *testing.T) { t.Errorf("Order.sans() = %v, want error; got none", got) return } - switch k := err.(type) { - case *Error: + var k *Error + if errors.As(err, &k) { assert.Equals(t, k.Type, tt.err.Type) assert.Equals(t, k.Detail, tt.err.Detail) assert.Equals(t, k.Status, tt.err.Status) assert.Equals(t, k.Err.Error(), tt.err.Err.Error()) assert.Equals(t, k.Detail, tt.err.Detail) - default: + } else { assert.FatalError(t, errors.New("unexpected error type")) } return diff --git a/api/api.go b/api/api.go index 75d26237..9c2f1f31 100644 --- a/api/api.go +++ b/api/api.go @@ -3,7 +3,7 @@ package api import ( "context" "crypto" - "crypto/dsa" //nolint + "crypto/dsa" //nolint:staticcheck // support legacy algorithms "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" @@ -40,6 +40,7 @@ type Authority interface { Root(shasum string) (*x509.Certificate, error) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) Renew(peer *x509.Certificate) ([]*x509.Certificate, error) + RenewContext(ctx context.Context, peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error) LoadProvisionerByName(string) (provisioner.Interface, error) @@ -49,6 +50,7 @@ type Authority interface { GetRoots() ([]*x509.Certificate, error) GetFederation() ([]*x509.Certificate, error) Version() authority.Version + GetCertificateRevocationList() ([]byte, error) } // mustAuthority will be replaced on unit tests. @@ -267,6 +269,7 @@ func Route(r Router) { r.MethodFunc("POST", "/renew", Renew) r.MethodFunc("POST", "/rekey", Rekey) r.MethodFunc("POST", "/revoke", Revoke) + r.MethodFunc("GET", "/crl", CRL) r.MethodFunc("GET", "/provisioners", Provisioners) r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", ProvisionerKey) r.MethodFunc("GET", "/roots", Roots) diff --git a/api/api_test.go b/api/api_test.go index 4c84871a..e24751b3 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -192,6 +192,7 @@ type mockAuthority struct { sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) renew func(cert *x509.Certificate) ([]*x509.Certificate, error) rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) + renewContext func(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error) loadProvisionerByName func(name string) (provisioner.Interface, error) getProvisioners func(nextCursor string, limit int) (provisioner.List, string, error) @@ -199,6 +200,7 @@ type mockAuthority struct { getEncryptedKey func(kid string) (string, error) getRoots func() ([]*x509.Certificate, error) getFederation func() ([]*x509.Certificate, error) + getCRL func() ([]byte, error) signSSH func(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error) @@ -212,6 +214,14 @@ type mockAuthority struct { version func() authority.Version } +func (m *mockAuthority) GetCertificateRevocationList() ([]byte, error) { + if m.getCRL != nil { + return m.getCRL() + } + + return m.ret1.([]byte), m.err +} + // TODO: remove once Authorize is deprecated. func (m *mockAuthority) Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error) { if m.authorize != nil { @@ -255,6 +265,13 @@ func (m *mockAuthority) Renew(cert *x509.Certificate) ([]*x509.Certificate, erro return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err } +func (m *mockAuthority) RenewContext(ctx context.Context, oldcert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { + if m.renewContext != nil { + return m.renewContext(ctx, oldcert, pk) + } + return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err +} + func (m *mockAuthority) Rekey(oldcert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { if m.rekey != nil { return m.rekey(oldcert, pk) @@ -772,6 +789,45 @@ func (m *mockProvisioner) AuthorizeSSHRekey(ctx context.Context, token string) ( return m.ret1.(*ssh.Certificate), m.ret2.([]provisioner.SignOption), m.err } +func Test_CRLGeneration(t *testing.T) { + tests := []struct { + name string + err error + statusCode int + expected []byte + }{ + {"empty", nil, http.StatusOK, nil}, + } + + chiCtx := chi.NewRouteContext() + req := httptest.NewRequest("GET", "http://example.com/crl", nil) + req = req.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockMustAuthority(t, &mockAuthority{ret1: tt.expected, err: tt.err}) + w := httptest.NewRecorder() + CRL(w, req) + res := w.Result() + + if res.StatusCode != tt.statusCode { + t.Errorf("caHandler.CRL StatusCode = %d, wants %d", res.StatusCode, tt.statusCode) + } + + body, err := io.ReadAll(res.Body) + res.Body.Close() + if err != nil { + t.Errorf("caHandler.Root unexpected error = %v", err) + } + if tt.statusCode == 200 { + if !bytes.Equal(bytes.TrimSpace(body), tt.expected) { + t.Errorf("caHandler.Root CRL = %s, wants %s", body, tt.expected) + } + } + }) + } +} + func Test_caHandler_Route(t *testing.T) { type fields struct { Authority Authority diff --git a/api/crl.go b/api/crl.go new file mode 100644 index 00000000..1a4d309a --- /dev/null +++ b/api/crl.go @@ -0,0 +1,32 @@ +package api + +import ( + "encoding/pem" + "net/http" + + "github.com/smallstep/certificates/api/render" +) + +// CRL is an HTTP handler that returns the current CRL in DER or PEM format +func CRL(w http.ResponseWriter, r *http.Request) { + crlBytes, err := mustAuthority(r.Context()).GetCertificateRevocationList() + if err != nil { + render.Error(w, err) + return + } + + _, formatAsPEM := r.URL.Query()["pem"] + if formatAsPEM { + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "X509 CRL", + Bytes: crlBytes, + }) + w.Header().Add("Content-Type", "application/x-pem-file") + w.Header().Add("Content-Disposition", "attachment; filename=\"crl.pem\"") + w.Write(pemBytes) + } else { + w.Header().Add("Content-Type", "application/pkix-crl") + w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"") + w.Write(crlBytes) + } +} diff --git a/api/log/log.go b/api/log/log.go index e5c8c45a..dc030c39 100644 --- a/api/log/log.go +++ b/api/log/log.go @@ -38,14 +38,10 @@ func Error(rw http.ResponseWriter, err error) { return } - e, ok := err.(StackTracedError) - if !ok { - e, ok = errors.Cause(err).(StackTracedError) - } - - if ok { + var st StackTracedError + if !errors.As(err, &st) { rl.WithFields(map[string]interface{}{ - "stack-trace": fmt.Sprintf("%+v", e.StackTrace()), + "stack-trace": fmt.Sprintf("%+v", st.StackTrace()), }) } } diff --git a/api/read/read_test.go b/api/read/read_test.go index 72100584..e46e7f61 100644 --- a/api/read/read_test.go +++ b/api/read/read_test.go @@ -41,8 +41,8 @@ func TestJSON(t *testing.T) { } if tt.wantErr { - e, ok := err.(*errs.Error) - if ok { + var e *errs.Error + if errors.As(err, &e) { if code := e.StatusCode(); code != 400 { t.Errorf("error.StatusCode() = %v, wants 400", code) } @@ -102,14 +102,15 @@ func TestProtoJSON(t *testing.T) { } if tt.wantErr { - switch err.(type) { - case badProtoJSONError: + var ( + ee *errs.Error + bpe badProtoJSONError + ) + switch { + case errors.As(err, &bpe): assert.Contains(t, err.Error(), "syntax error") - case *errs.Error: - var ee *errs.Error - if errors.As(err, &ee) { - assert.Equal(t, http.StatusBadRequest, ee.Status) - } + case errors.As(err, &ee): + assert.Equal(t, http.StatusBadRequest, ee.Status) } return } diff --git a/api/render/render.go b/api/render/render.go index 9df4c791..81a7a02e 100644 --- a/api/render/render.go +++ b/api/render/render.go @@ -4,6 +4,7 @@ package render import ( "bytes" "encoding/json" + "errors" "net/http" "google.golang.org/protobuf/encoding/protojson" @@ -77,8 +78,9 @@ type RenderableError interface { func Error(w http.ResponseWriter, err error) { log.Error(w, err) - if e, ok := err.(RenderableError); ok { - e.Render(w) + var r RenderableError + if errors.As(err, &r) { + r.Render(w) return } @@ -105,17 +107,18 @@ func statusCodeFromError(err error) (code int) { } for err != nil { - if sc, ok := err.(StatusCodedError); ok { + var sc StatusCodedError + if errors.As(err, &sc) { code = sc.StatusCode() break } - cause, ok := err.(causer) - if !ok { + var c causer + if !errors.As(err, &c) { break } - err = cause.Cause() + err = c.Cause() } return diff --git a/api/renew.go b/api/renew.go index 6e9f680f..1b9ed95f 100644 --- a/api/renew.go +++ b/api/renew.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/smallstep/certificates/api/render" + "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/errs" ) @@ -17,14 +18,22 @@ const ( // Renew uses the information of certificate in the TLS connection to create a // new one. func Renew(w http.ResponseWriter, r *http.Request) { - cert, err := getPeerCertificate(r) + ctx := r.Context() + + // Get the leaf certificate from the peer or the token. + cert, token, err := getPeerCertificate(r) if err != nil { render.Error(w, err) return } - a := mustAuthority(r.Context()) - certChain, err := a.Renew(cert) + // The token can be used by RAs to renew a certificate. + if token != "" { + ctx = authority.NewTokenContext(ctx, token) + } + + a := mustAuthority(ctx) + certChain, err := a.RenewContext(ctx, cert, nil) if err != nil { render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew")) return @@ -44,15 +53,16 @@ func Renew(w http.ResponseWriter, r *http.Request) { }, http.StatusCreated) } -func getPeerCertificate(r *http.Request) (*x509.Certificate, error) { +func getPeerCertificate(r *http.Request) (*x509.Certificate, string, error) { if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 { - return r.TLS.PeerCertificates[0], nil + return r.TLS.PeerCertificates[0], "", nil } if s := r.Header.Get(authorizationHeader); s != "" { if parts := strings.SplitN(s, bearerScheme+" ", 2); len(parts) == 2 { ctx := r.Context() - return mustAuthority(ctx).AuthorizeRenewToken(ctx, parts[1]) + peer, err := mustAuthority(ctx).AuthorizeRenewToken(ctx, parts[1]) + return peer, parts[1], err } } - return nil, errs.BadRequest("missing client certificate") + return nil, "", errs.BadRequest("missing client certificate") } diff --git a/api/revoke_test.go b/api/revoke_test.go index 0955244e..763986b0 100644 --- a/api/revoke_test.go +++ b/api/revoke_test.go @@ -62,12 +62,12 @@ func TestRevokeRequestValidate(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { if err := tc.rr.Validate(); err != nil { - switch v := err.(type) { - case *errs.Error: - assert.HasPrefix(t, v.Error(), tc.err.Error()) - assert.Equals(t, v.StatusCode(), tc.err.Status) - default: - t.Errorf("unexpected error type: %T", v) + var ee *errs.Error + if errors.As(err, &ee) { + assert.HasPrefix(t, ee.Error(), tc.err.Error()) + assert.Equals(t, ee.StatusCode(), tc.err.Status) + } else { + t.Errorf("unexpected error type: %T", err) } } else { assert.Nil(t, tc.err) diff --git a/authority/admin/api/acme.go b/authority/admin/api/acme.go index db393e9a..0ce8d4d7 100644 --- a/authority/admin/api/acme.go +++ b/authority/admin/api/acme.go @@ -84,7 +84,6 @@ func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, r * } func eakToLinked(k *acme.ExternalAccountKey) *linkedca.EABKey { - if k == nil { return nil } diff --git a/authority/admin/api/admin_test.go b/authority/admin/api/admin_test.go index ecb95244..3d4cdd9c 100644 --- a/authority/admin/api/admin_test.go +++ b/authority/admin/api/admin_test.go @@ -229,11 +229,13 @@ func TestCreateAdminRequest_Validate(t *testing.T) { if err != nil { assert.Type(t, &admin.Error{}, err) - adminErr, _ := err.(*admin.Error) - assert.Equals(t, tt.err.Type, adminErr.Type) - assert.Equals(t, tt.err.Detail, adminErr.Detail) - assert.Equals(t, tt.err.Status, adminErr.Status) - assert.Equals(t, tt.err.Message, adminErr.Message) + var adminErr *admin.Error + if assert.True(t, errors.As(err, &adminErr)) { + assert.Equals(t, tt.err.Type, adminErr.Type) + assert.Equals(t, tt.err.Detail, adminErr.Detail) + assert.Equals(t, tt.err.Status, adminErr.Status) + assert.Equals(t, tt.err.Message, adminErr.Message) + } } }) } @@ -278,11 +280,13 @@ func TestUpdateAdminRequest_Validate(t *testing.T) { if err != nil { assert.Type(t, &admin.Error{}, err) - adminErr, _ := err.(*admin.Error) - assert.Equals(t, tt.err.Type, adminErr.Type) - assert.Equals(t, tt.err.Detail, adminErr.Detail) - assert.Equals(t, tt.err.Status, adminErr.Status) - assert.Equals(t, tt.err.Message, adminErr.Message) + var ae *admin.Error + if assert.True(t, errors.As(err, &ae)) { + assert.Equals(t, tt.err.Type, ae.Type) + assert.Equals(t, tt.err.Detail, ae.Detail) + assert.Equals(t, tt.err.Status, ae.Status) + assert.Equals(t, tt.err.Message, ae.Message) + } } }) } diff --git a/authority/admin/api/handler.go b/authority/admin/api/handler.go index 1e5919ce..a4faf936 100644 --- a/authority/admin/api/handler.go +++ b/authority/admin/api/handler.go @@ -4,41 +4,47 @@ import ( "context" "net/http" - "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority" - "github.com/smallstep/certificates/authority/admin" ) -// Handler is the Admin API request handler. -type Handler struct { - acmeResponder ACMEAdminResponder - policyResponder PolicyAdminResponder +var mustAuthority = func(ctx context.Context) adminAuthority { + return authority.MustFromContext(ctx) } -// Route traffic and implement the Router interface. -// -// Deprecated: use Route(r api.Router, acmeResponder ACMEAdminResponder, policyResponder PolicyAdminResponder) -func (h *Handler) Route(r api.Router) { - Route(r, h.acmeResponder, h.policyResponder) +type router struct { + acmeResponder ACMEAdminResponder + policyResponder PolicyAdminResponder + webhookResponder WebhookAdminResponder } -// NewHandler returns a new Authority Config Handler. -// -// Deprecated: use Route(r api.Router, acmeResponder ACMEAdminResponder, policyResponder PolicyAdminResponder) -func NewHandler(auth adminAuthority, adminDB admin.DB, acmeDB acme.DB, acmeResponder ACMEAdminResponder, policyResponder PolicyAdminResponder) api.RouterHandler { - return &Handler{ - acmeResponder: acmeResponder, - policyResponder: policyResponder, +type RouterOption func(*router) + +func WithACMEResponder(acmeResponder ACMEAdminResponder) RouterOption { + return func(r *router) { + r.acmeResponder = acmeResponder } } -var mustAuthority = func(ctx context.Context) adminAuthority { - return authority.MustFromContext(ctx) +func WithPolicyResponder(policyResponder PolicyAdminResponder) RouterOption { + return func(r *router) { + r.policyResponder = policyResponder + } +} + +func WithWebhookResponder(webhookResponder WebhookAdminResponder) RouterOption { + return func(r *router) { + r.webhookResponder = webhookResponder + } } // Route traffic and implement the Router interface. -func Route(r api.Router, acmeResponder ACMEAdminResponder, policyResponder PolicyAdminResponder) { +func Route(r api.Router, options ...RouterOption) { + router := &router{} + for _, fn := range options { + fn(router) + } + authnz := func(next http.HandlerFunc) http.HandlerFunc { return extractAuthorizeTokenAdmin(requireAPIEnabled(next)) } @@ -67,6 +73,10 @@ func Route(r api.Router, acmeResponder ACMEAdminResponder, policyResponder Polic return authnz(disabledInStandalone(loadProvisionerByName(requireEABEnabled(loadExternalAccountKey(next))))) } + webhookMiddleware := func(next http.HandlerFunc) http.HandlerFunc { + return authnz(loadProvisionerByName(next)) + } + // Provisioners r.MethodFunc("GET", "/provisioners/{name}", authnz(GetProvisioner)) r.MethodFunc("GET", "/provisioners", authnz(GetProvisioners)) @@ -82,36 +92,42 @@ func Route(r api.Router, acmeResponder ACMEAdminResponder, policyResponder Polic r.MethodFunc("DELETE", "/admins/{id}", authnz(DeleteAdmin)) // ACME responder - if acmeResponder != nil { + if router.acmeResponder != nil { // ACME External Account Binding Keys - r.MethodFunc("GET", "/acme/eab/{provisionerName}/{reference}", acmeEABMiddleware(acmeResponder.GetExternalAccountKeys)) - r.MethodFunc("GET", "/acme/eab/{provisionerName}", acmeEABMiddleware(acmeResponder.GetExternalAccountKeys)) - r.MethodFunc("POST", "/acme/eab/{provisionerName}", acmeEABMiddleware(acmeResponder.CreateExternalAccountKey)) - r.MethodFunc("DELETE", "/acme/eab/{provisionerName}/{id}", acmeEABMiddleware(acmeResponder.DeleteExternalAccountKey)) + r.MethodFunc("GET", "/acme/eab/{provisionerName}/{reference}", acmeEABMiddleware(router.acmeResponder.GetExternalAccountKeys)) + r.MethodFunc("GET", "/acme/eab/{provisionerName}", acmeEABMiddleware(router.acmeResponder.GetExternalAccountKeys)) + r.MethodFunc("POST", "/acme/eab/{provisionerName}", acmeEABMiddleware(router.acmeResponder.CreateExternalAccountKey)) + r.MethodFunc("DELETE", "/acme/eab/{provisionerName}/{id}", acmeEABMiddleware(router.acmeResponder.DeleteExternalAccountKey)) } // Policy responder - if policyResponder != nil { + if router.policyResponder != nil { // Policy - Authority - r.MethodFunc("GET", "/policy", authorityPolicyMiddleware(policyResponder.GetAuthorityPolicy)) - r.MethodFunc("POST", "/policy", authorityPolicyMiddleware(policyResponder.CreateAuthorityPolicy)) - r.MethodFunc("PUT", "/policy", authorityPolicyMiddleware(policyResponder.UpdateAuthorityPolicy)) - r.MethodFunc("DELETE", "/policy", authorityPolicyMiddleware(policyResponder.DeleteAuthorityPolicy)) + r.MethodFunc("GET", "/policy", authorityPolicyMiddleware(router.policyResponder.GetAuthorityPolicy)) + r.MethodFunc("POST", "/policy", authorityPolicyMiddleware(router.policyResponder.CreateAuthorityPolicy)) + r.MethodFunc("PUT", "/policy", authorityPolicyMiddleware(router.policyResponder.UpdateAuthorityPolicy)) + r.MethodFunc("DELETE", "/policy", authorityPolicyMiddleware(router.policyResponder.DeleteAuthorityPolicy)) // Policy - Provisioner - r.MethodFunc("GET", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(policyResponder.GetProvisionerPolicy)) - r.MethodFunc("POST", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(policyResponder.CreateProvisionerPolicy)) - r.MethodFunc("PUT", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(policyResponder.UpdateProvisionerPolicy)) - r.MethodFunc("DELETE", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(policyResponder.DeleteProvisionerPolicy)) + r.MethodFunc("GET", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(router.policyResponder.GetProvisionerPolicy)) + r.MethodFunc("POST", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(router.policyResponder.CreateProvisionerPolicy)) + r.MethodFunc("PUT", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(router.policyResponder.UpdateProvisionerPolicy)) + r.MethodFunc("DELETE", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(router.policyResponder.DeleteProvisionerPolicy)) // Policy - ACME Account - r.MethodFunc("GET", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(policyResponder.GetACMEAccountPolicy)) - r.MethodFunc("GET", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(policyResponder.GetACMEAccountPolicy)) - r.MethodFunc("POST", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(policyResponder.CreateACMEAccountPolicy)) - r.MethodFunc("POST", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(policyResponder.CreateACMEAccountPolicy)) - r.MethodFunc("PUT", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(policyResponder.UpdateACMEAccountPolicy)) - r.MethodFunc("PUT", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(policyResponder.UpdateACMEAccountPolicy)) - r.MethodFunc("DELETE", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(policyResponder.DeleteACMEAccountPolicy)) - r.MethodFunc("DELETE", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(policyResponder.DeleteACMEAccountPolicy)) + r.MethodFunc("GET", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(router.policyResponder.GetACMEAccountPolicy)) + r.MethodFunc("GET", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(router.policyResponder.GetACMEAccountPolicy)) + r.MethodFunc("POST", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(router.policyResponder.CreateACMEAccountPolicy)) + r.MethodFunc("POST", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(router.policyResponder.CreateACMEAccountPolicy)) + r.MethodFunc("PUT", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(router.policyResponder.UpdateACMEAccountPolicy)) + r.MethodFunc("PUT", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(router.policyResponder.UpdateACMEAccountPolicy)) + r.MethodFunc("DELETE", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(router.policyResponder.DeleteACMEAccountPolicy)) + r.MethodFunc("DELETE", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(router.policyResponder.DeleteACMEAccountPolicy)) + } + + if router.webhookResponder != nil { + r.MethodFunc("POST", "/provisioners/{provisionerName}/webhooks", webhookMiddleware(router.webhookResponder.CreateProvisionerWebhook)) + r.MethodFunc("PUT", "/provisioners/{provisionerName}/webhooks/{webhookName}", webhookMiddleware(router.webhookResponder.UpdateProvisionerWebhook)) + r.MethodFunc("DELETE", "/provisioners/{provisionerName}/webhooks/{webhookName}", webhookMiddleware(router.webhookResponder.DeleteProvisionerWebhook)) } } diff --git a/authority/admin/api/middleware.go b/authority/admin/api/middleware.go index 780cfb65..3c1b040a 100644 --- a/authority/admin/api/middleware.go +++ b/authority/admin/api/middleware.go @@ -30,7 +30,6 @@ func requireAPIEnabled(next http.HandlerFunc) http.HandlerFunc { // extractAuthorizeTokenAdmin is a middleware that extracts and caches the bearer token. func extractAuthorizeTokenAdmin(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - tok := r.Header.Get("Authorization") if tok == "" { render.Error(w, admin.NewError(admin.ErrorUnauthorizedType, diff --git a/authority/admin/api/policy.go b/authority/admin/api/policy.go index a478c83c..89744893 100644 --- a/authority/admin/api/policy.go +++ b/authority/admin/api/policy.go @@ -50,7 +50,8 @@ func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *ht auth := mustAuthority(ctx) authorityPolicy, err := auth.GetAuthorityPolicy(r.Context()) - if ae, ok := err.(*admin.Error); ok && !ae.IsType(admin.ErrorNotFoundType) { + var ae *admin.Error + if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy")) return } @@ -74,7 +75,8 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r auth := mustAuthority(ctx) authorityPolicy, err := auth.GetAuthorityPolicy(ctx) - if ae, ok := err.(*admin.Error); ok && !ae.IsType(admin.ErrorNotFoundType) { + var ae *admin.Error + if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy")) return } @@ -125,7 +127,8 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r auth := mustAuthority(ctx) authorityPolicy, err := auth.GetAuthorityPolicy(ctx) - if ae, ok := err.(*admin.Error); ok && !ae.IsType(admin.ErrorNotFoundType) { + var ae *admin.Error + if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy")) return } @@ -175,7 +178,8 @@ func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r auth := mustAuthority(ctx) authorityPolicy, err := auth.GetAuthorityPolicy(ctx) - if ae, ok := err.(*admin.Error); ok && !ae.IsType(admin.ErrorNotFoundType) { + var ae *admin.Error + if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy")) return } @@ -468,7 +472,6 @@ func isBadRequest(err error) bool { } func validatePolicy(p *linkedca.Policy) error { - // convert the policy; return early if nil options := policy.LinkedToCertificates(p) if options == nil { diff --git a/authority/admin/api/webhook.go b/authority/admin/api/webhook.go new file mode 100644 index 00000000..f73f6806 --- /dev/null +++ b/authority/admin/api/webhook.go @@ -0,0 +1,235 @@ +package api + +import ( + "encoding/base64" + "fmt" + "net/http" + "net/url" + + "github.com/go-chi/chi" + "github.com/smallstep/certificates/api/read" + "github.com/smallstep/certificates/api/render" + "github.com/smallstep/certificates/authority/admin" + "go.step.sm/crypto/randutil" + "go.step.sm/linkedca" +) + +// WebhookAdminResponder is the interface responsible for writing webhook admin +// responses. +type WebhookAdminResponder interface { + CreateProvisionerWebhook(w http.ResponseWriter, r *http.Request) + UpdateProvisionerWebhook(w http.ResponseWriter, r *http.Request) + DeleteProvisionerWebhook(w http.ResponseWriter, r *http.Request) +} + +// webhoookAdminResponder implements WebhookAdminResponder +type webhookAdminResponder struct{} + +// NewWebhookAdminResponder returns a new WebhookAdminResponder +func NewWebhookAdminResponder() WebhookAdminResponder { + return &webhookAdminResponder{} +} + +func validateWebhook(webhook *linkedca.Webhook) error { + if webhook == nil { + return nil + } + + // name + if webhook.Name == "" { + return admin.NewError(admin.ErrorBadRequestType, "webhook name is required") + } + + // url + parsedURL, err := url.Parse(webhook.Url) + if err != nil { + return admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid") + } + if parsedURL.Host == "" { + return admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid") + } + if parsedURL.Scheme != "https" { + return admin.NewError(admin.ErrorBadRequestType, "webhook url must use https") + } + if parsedURL.User != nil { + return admin.NewError(admin.ErrorBadRequestType, "webhook url may not contain username or password") + } + + // kind + switch webhook.Kind { + case linkedca.Webhook_ENRICHING, linkedca.Webhook_AUTHORIZING: + default: + return admin.NewError(admin.ErrorBadRequestType, "webhook kind is invalid") + } + + return nil +} + +func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + auth := mustAuthority(ctx) + prov := linkedca.MustProvisionerFromContext(ctx) + + var newWebhook = new(linkedca.Webhook) + if err := read.ProtoJSON(r.Body, newWebhook); err != nil { + render.Error(w, err) + return + } + + if err := validateWebhook(newWebhook); err != nil { + render.Error(w, err) + return + } + if newWebhook.Secret != "" { + err := admin.NewError(admin.ErrorBadRequestType, "webhook secret must not be set") + render.Error(w, err) + return + } + if newWebhook.Id != "" { + err := admin.NewError(admin.ErrorBadRequestType, "webhook ID must not be set") + render.Error(w, err) + return + } + + id, err := randutil.UUIDv4() + if err != nil { + render.Error(w, admin.WrapErrorISE(err, "error generating webhook id")) + return + } + newWebhook.Id = id + + // verify the name is unique + for _, wh := range prov.Webhooks { + if wh.Name == newWebhook.Name { + err := admin.NewError(admin.ErrorConflictType, "provisioner %q already has a webhook with the name %q", prov.Name, newWebhook.Name) + render.Error(w, err) + return + } + } + + secret, err := randutil.Bytes(64) + if err != nil { + render.Error(w, admin.WrapErrorISE(err, "error generating webhook secret")) + return + } + newWebhook.Secret = base64.StdEncoding.EncodeToString(secret) + + prov.Webhooks = append(prov.Webhooks, newWebhook) + + if err := auth.UpdateProvisioner(ctx, prov); err != nil { + if isBadRequest(err) { + render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner webhook")) + return + } + + render.Error(w, admin.WrapErrorISE(err, "error creating provisioner webhook")) + return + } + + render.ProtoJSONStatus(w, newWebhook, http.StatusCreated) +} + +func (war *webhookAdminResponder) DeleteProvisionerWebhook(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + auth := mustAuthority(ctx) + prov := linkedca.MustProvisionerFromContext(ctx) + + webhookName := chi.URLParam(r, "webhookName") + + found := false + for i, wh := range prov.Webhooks { + if wh.Name == webhookName { + prov.Webhooks = append(prov.Webhooks[0:i], prov.Webhooks[i+1:]...) + found = true + break + } + } + if !found { + render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) + return + } + + if err := auth.UpdateProvisioner(ctx, prov); err != nil { + if isBadRequest(err) { + render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error deleting provisioner webhook")) + return + } + + render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner webhook")) + return + } + + render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) +} + +func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + auth := mustAuthority(ctx) + prov := linkedca.MustProvisionerFromContext(ctx) + + var newWebhook = new(linkedca.Webhook) + if err := read.ProtoJSON(r.Body, newWebhook); err != nil { + render.Error(w, err) + return + } + + if err := validateWebhook(newWebhook); err != nil { + render.Error(w, err) + return + } + + found := false + for i, wh := range prov.Webhooks { + if wh.Name != newWebhook.Name { + continue + } + if newWebhook.Secret != "" && newWebhook.Secret != wh.Secret { + err := admin.NewError(admin.ErrorBadRequestType, "webhook secret cannot be updated") + render.Error(w, err) + return + } + newWebhook.Secret = wh.Secret + if newWebhook.Id != "" && newWebhook.Id != wh.Id { + err := admin.NewError(admin.ErrorBadRequestType, "webhook ID cannot be updated") + render.Error(w, err) + return + } + newWebhook.Id = wh.Id + prov.Webhooks[i] = newWebhook + found = true + break + } + if !found { + msg := fmt.Sprintf("provisioner %q has no webhook with the name %q", prov.Name, newWebhook.Name) + err := admin.NewError(admin.ErrorNotFoundType, msg) + render.Error(w, err) + return + } + + if err := auth.UpdateProvisioner(ctx, prov); err != nil { + if isBadRequest(err) { + render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner webhook")) + return + } + + render.Error(w, admin.WrapErrorISE(err, "error updating provisioner webhook")) + return + } + + // Return a copy without the signing secret. Include the client-supplied + // auth secrets since those may have been updated in this request and we + // should show in the response that they changed + whResponse := &linkedca.Webhook{ + Id: newWebhook.Id, + Name: newWebhook.Name, + Url: newWebhook.Url, + Kind: newWebhook.Kind, + CertType: newWebhook.CertType, + Auth: newWebhook.Auth, + DisableTlsClientAuth: newWebhook.DisableTlsClientAuth, + } + render.ProtoJSONStatus(w, whResponse, http.StatusCreated) +} diff --git a/authority/admin/api/webhook_test.go b/authority/admin/api/webhook_test.go new file mode 100644 index 00000000..baac2c11 --- /dev/null +++ b/authority/admin/api/webhook_test.go @@ -0,0 +1,668 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http/httptest" + "strings" + "testing" + + "github.com/go-chi/chi" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/admin" + "github.com/stretchr/testify/assert" + "go.step.sm/linkedca" + "google.golang.org/protobuf/encoding/protojson" +) + +// ignore secret and id since those are set by the server +func assertEqualWebhook(t *testing.T, a, b *linkedca.Webhook) { + assert.Equal(t, a.Name, b.Name) + assert.Equal(t, a.Url, b.Url) + assert.Equal(t, a.Kind, b.Kind) + assert.Equal(t, a.CertType, b.CertType) + assert.Equal(t, a.DisableTlsClientAuth, b.DisableTlsClientAuth) + + assert.Equal(t, a.GetAuth(), b.GetAuth()) +} + +func TestWebhookAdminResponder_CreateProvisionerWebhook(t *testing.T) { + type test struct { + auth adminAuthority + body []byte + ctx context.Context + err *admin.Error + response *linkedca.Webhook + statusCode int + } + var tests = map[string]func(t *testing.T) test{ + "fail/existing-webhook": func(t *testing.T) test { + webhook := &linkedca.Webhook{ + Name: "already-exists", + Url: "https://example.com", + } + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{webhook}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + err := admin.NewError(admin.ErrorConflictType, `provisioner "provName" already has a webhook with the name "already-exists"`) + err.Message = `provisioner "provName" already has a webhook with the name "already-exists"` + body := []byte(` + { + "name": "already-exists", + "url": "https://example.com", + "kind": "ENRICHING" + }`) + return test{ + ctx: ctx, + body: body, + err: err, + statusCode: 409, + } + }, + "fail/read.ProtoJSON": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "proto: syntax error (line 1:2): invalid value ?") + adminErr.Message = "proto: syntax error (line 1:2): invalid value ?" + body := []byte("{?}") + return test{ + ctx: ctx, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/missing-name": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook name is required") + adminErr.Message = "webhook name is required" + body := []byte(`{"url": "https://example.com", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/missing-url": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid") + adminErr.Message = "webhook url is invalid" + body := []byte(`{"name": "metadata", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/relative-url": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid") + adminErr.Message = "webhook url is invalid" + body := []byte(`{"name": "metadata", "url": "example.com/path", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/http-url": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url must use https") + adminErr.Message = "webhook url must use https" + body := []byte(`{"name": "metadata", "url": "http://example.com", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/basic-auth-in-url": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url may not contain username or password") + adminErr.Message = "webhook url may not contain username or password" + body := []byte(` + { + "name": "metadata", + "url": "https://user:pass@example.com", + "kind": "ENRICHING" + }`) + return test{ + ctx: ctx, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/secret-in-request": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook secret must not be set") + adminErr.Message = "webhook secret must not be set" + body := []byte(` + { + "name": "metadata", + "url": "https://example.com", + "kind": "ENRICHING", + "secret": "secret" + }`) + return test{ + ctx: ctx, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/auth.UpdateProvisioner-error": func(t *testing.T) test { + adm := &linkedca.Admin{ + Subject: "step", + } + prov := &linkedca.Provisioner{ + Name: "provName", + } + ctx := linkedca.NewContextWithAdmin(context.Background(), adm) + ctx = linkedca.NewContextWithProvisioner(ctx, prov) + adminErr := admin.NewError(admin.ErrorServerInternalType, "error creating provisioner webhook: force") + adminErr.Message = "error creating provisioner webhook: force" + body := []byte(`{"name": "metadata", "url": "https://example.com", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + auth: &mockAdminAuthority{ + MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error { + return &authority.PolicyError{ + Typ: authority.StoreFailure, + Err: errors.New("force"), + } + }, + }, + body: body, + err: adminErr, + statusCode: 500, + } + }, + "ok": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + body := []byte(`{"name": "metadata", "url": "https://example.com", "kind": "ENRICHING", "certType": "X509"}`) + return test{ + ctx: ctx, + auth: &mockAdminAuthority{ + MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error { + assert.Equal(t, linkedca.Webhook_X509, nu.Webhooks[0].CertType) + return nil + }, + }, + body: body, + response: &linkedca.Webhook{ + Name: "metadata", + Url: "https://example.com", + Kind: linkedca.Webhook_ENRICHING, + CertType: linkedca.Webhook_X509, + }, + statusCode: 201, + } + }, + } + for name, prep := range tests { + tc := prep(t) + t.Run(name, func(t *testing.T) { + mockMustAuthority(t, tc.auth) + ctx := admin.NewContext(tc.ctx, &admin.MockDB{}) + war := NewWebhookAdminResponder() + + req := httptest.NewRequest("POST", "/foo", io.NopCloser(bytes.NewBuffer(tc.body))) + req = req.WithContext(ctx) + w := httptest.NewRecorder() + + war.CreateProvisionerWebhook(w, req) + res := w.Result() + + assert.Equal(t, tc.statusCode, res.StatusCode) + + if res.StatusCode >= 400 { + + body, err := io.ReadAll(res.Body) + res.Body.Close() + assert.NoError(t, err) + + ae := testAdminError{} + assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &ae)) + + assert.Equal(t, tc.err.Type, ae.Type) + assert.Equal(t, tc.err.StatusCode(), res.StatusCode) + assert.Equal(t, tc.err.Detail, ae.Detail) + assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"]) + + // when the error message starts with "proto", we expect it to have + // a syntax error (in the tests). If the message doesn't start with "proto", + // we expect a full string match. + if strings.HasPrefix(tc.err.Message, "proto:") { + assert.True(t, strings.Contains(ae.Message, "syntax error")) + } else { + assert.Equal(t, tc.err.Message, ae.Message) + } + + return + } + + resp := &linkedca.Webhook{} + body, err := io.ReadAll(res.Body) + assert.NoError(t, err) + assert.NoError(t, protojson.Unmarshal(body, resp)) + + assertEqualWebhook(t, tc.response, resp) + assert.NotEmpty(t, resp.Secret) + assert.NotEmpty(t, resp.Id) + }) + } +} + +func TestWebhookAdminResponder_DeleteProvisionerWebhook(t *testing.T) { + type test struct { + auth adminAuthority + err *admin.Error + statusCode int + provisionerWebhooks []*linkedca.Webhook + webhookName string + } + var tests = map[string]func(t *testing.T) test{ + "fail/auth.UpdateProvisioner-error": func(t *testing.T) test { + adminErr := admin.NewError(admin.ErrorServerInternalType, "error deleting provisioner webhook: force") + adminErr.Message = "error deleting provisioner webhook: force" + return test{ + err: adminErr, + auth: &mockAdminAuthority{ + MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error { + return &authority.PolicyError{ + Typ: authority.StoreFailure, + Err: errors.New("force"), + } + }, + }, + statusCode: 500, + webhookName: "my-webhook", + provisionerWebhooks: []*linkedca.Webhook{ + {Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}, + }, + } + }, + "ok/not-found": func(t *testing.T) test { + return test{ + statusCode: 200, + webhookName: "no-exists", + provisionerWebhooks: nil, + } + }, + "ok": func(t *testing.T) test { + return test{ + statusCode: 200, + webhookName: "exists", + auth: &mockAdminAuthority{ + MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error { + assert.Equal(t, nu.Webhooks, []*linkedca.Webhook{ + {Name: "my-2nd-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}, + }) + return nil + }, + }, + provisionerWebhooks: []*linkedca.Webhook{ + {Name: "exists", Url: "https.example.com", Kind: linkedca.Webhook_ENRICHING}, + {Name: "my-2nd-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}, + }, + } + }, + } + for name, prep := range tests { + tc := prep(t) + t.Run(name, func(t *testing.T) { + mockMustAuthority(t, tc.auth) + + chiCtx := chi.NewRouteContext() + chiCtx.URLParams.Add("webhookName", tc.webhookName) + ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx) + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: tc.provisionerWebhooks, + } + ctx = linkedca.NewContextWithProvisioner(ctx, prov) + ctx = admin.NewContext(ctx, &admin.MockDB{}) + req := httptest.NewRequest("DELETE", "/foo", nil).WithContext(ctx) + + war := NewWebhookAdminResponder() + + w := httptest.NewRecorder() + + war.DeleteProvisionerWebhook(w, req) + res := w.Result() + + assert.Equal(t, tc.statusCode, res.StatusCode) + + if res.StatusCode >= 400 { + + body, err := io.ReadAll(res.Body) + res.Body.Close() + assert.NoError(t, err) + + ae := testAdminError{} + assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &ae)) + + assert.Equal(t, tc.err.Type, ae.Type) + assert.Equal(t, tc.err.StatusCode(), res.StatusCode) + assert.Equal(t, tc.err.Detail, ae.Detail) + assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"]) + + // when the error message starts with "proto", we expect it to have + // a syntax error (in the tests). If the message doesn't start with "proto", + // we expect a full string match. + if strings.HasPrefix(tc.err.Message, "proto:") { + assert.True(t, strings.Contains(ae.Message, "syntax error")) + } else { + assert.Equal(t, tc.err.Message, ae.Message) + } + + return + } + + body, err := io.ReadAll(res.Body) + assert.NoError(t, err) + res.Body.Close() + response := DeleteResponse{} + assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &response)) + assert.Equal(t, "ok", response.Status) + assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"]) + }) + } +} + +func TestWebhookAdminResponder_UpdateProvisionerWebhook(t *testing.T) { + type test struct { + auth adminAuthority + adminDB admin.DB + body []byte + ctx context.Context + err *admin.Error + response *linkedca.Webhook + statusCode int + } + var tests = map[string]func(t *testing.T) test{ + "fail/not-found": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "exists", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + err := admin.NewError(admin.ErrorNotFoundType, `provisioner "provName" has no webhook with the name "no-exists"`) + err.Message = `provisioner "provName" has no webhook with the name "no-exists"` + body := []byte(` + { + "name": "no-exists", + "url": "https://example.com", + "kind": "ENRICHING" + }`) + return test{ + ctx: ctx, + adminDB: &admin.MockDB{}, + body: body, + err: err, + statusCode: 404, + } + }, + "fail/read.ProtoJSON": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "proto: syntax error (line 1:2): invalid value ?") + adminErr.Message = "proto: syntax error (line 1:2): invalid value ?" + body := []byte("{?}") + return test{ + ctx: ctx, + adminDB: &admin.MockDB{}, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/missing-name": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook name is required") + adminErr.Message = "webhook name is required" + body := []byte(`{"url": "https://example.com", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + adminDB: &admin.MockDB{}, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/missing-url": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid") + adminErr.Message = "webhook url is invalid" + body := []byte(`{"name": "metadata", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + adminDB: &admin.MockDB{}, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/relative-url": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid") + adminErr.Message = "webhook url is invalid" + body := []byte(`{"name": "metadata", "url": "example.com/path", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + adminDB: &admin.MockDB{}, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/http-url": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url must use https") + adminErr.Message = "webhook url must use https" + body := []byte(`{"name": "metadata", "url": "http://example.com", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + adminDB: &admin.MockDB{}, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/basic-auth-in-url": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url may not contain username or password") + adminErr.Message = "webhook url may not contain username or password" + body := []byte(` + { + "name": "my-webhook", + "url": "https://user:pass@example.com", + "kind": "ENRICHING" + }`) + return test{ + ctx: ctx, + adminDB: &admin.MockDB{}, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/different-secret-in-request": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING, Secret: "c2VjcmV0"}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook secret cannot be updated") + adminErr.Message = "webhook secret cannot be updated" + body := []byte(` + { + "name": "my-webhook", + "url": "https://example.com", + "kind": "ENRICHING", + "secret": "secret" + }`) + return test{ + ctx: ctx, + body: body, + err: adminErr, + statusCode: 400, + } + }, + "fail/auth.UpdateProvisioner-error": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + adminErr := admin.NewError(admin.ErrorServerInternalType, "error updating provisioner webhook: force") + adminErr.Message = "error updating provisioner webhook: force" + body := []byte(`{"name": "my-webhook", "url": "https://example.com", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + adminDB: &admin.MockDB{}, + auth: &mockAdminAuthority{ + MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error { + return &authority.PolicyError{ + Typ: authority.StoreFailure, + Err: errors.New("force"), + } + }, + }, + body: body, + err: adminErr, + statusCode: 500, + } + }, + "ok": func(t *testing.T) test { + prov := &linkedca.Provisioner{ + Name: "provName", + Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}}, + } + ctx := linkedca.NewContextWithProvisioner(context.Background(), prov) + body := []byte(`{"name": "my-webhook", "url": "https://example.com", "kind": "ENRICHING"}`) + return test{ + ctx: ctx, + adminDB: &admin.MockDB{}, + auth: &mockAdminAuthority{ + MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error { + return nil + }, + }, + body: body, + response: &linkedca.Webhook{ + Name: "my-webhook", + Url: "https://example.com", + Kind: linkedca.Webhook_ENRICHING, + }, + statusCode: 201, + } + }, + } + for name, prep := range tests { + tc := prep(t) + t.Run(name, func(t *testing.T) { + mockMustAuthority(t, tc.auth) + ctx := admin.NewContext(tc.ctx, tc.adminDB) + war := NewWebhookAdminResponder() + + req := httptest.NewRequest("PUT", "/foo", io.NopCloser(bytes.NewBuffer(tc.body))) + req = req.WithContext(ctx) + w := httptest.NewRecorder() + + war.UpdateProvisionerWebhook(w, req) + res := w.Result() + + assert.Equal(t, tc.statusCode, res.StatusCode) + + if res.StatusCode >= 400 { + + body, err := io.ReadAll(res.Body) + res.Body.Close() + assert.NoError(t, err) + + ae := testAdminError{} + assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &ae)) + + assert.Equal(t, tc.err.Type, ae.Type) + assert.Equal(t, tc.err.StatusCode(), res.StatusCode) + assert.Equal(t, tc.err.Detail, ae.Detail) + assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"]) + + // when the error message starts with "proto", we expect it to have + // a syntax error (in the tests). If the message doesn't start with "proto", + // we expect a full string match. + if strings.HasPrefix(tc.err.Message, "proto:") { + assert.True(t, strings.Contains(ae.Message, "syntax error")) + } else { + assert.Equal(t, tc.err.Message, ae.Message) + } + + return + } + + resp := &linkedca.Webhook{} + body, err := io.ReadAll(res.Body) + assert.NoError(t, err) + assert.NoError(t, protojson.Unmarshal(body, resp)) + + assertEqualWebhook(t, tc.response, resp) + }) + } +} diff --git a/authority/admin/db/nosql/admin.go b/authority/admin/db/nosql/admin.go index 6bb6bdd1..c0f90c2f 100644 --- a/authority/admin/db/nosql/admin.go +++ b/authority/admin/db/nosql/admin.go @@ -111,14 +111,14 @@ func (db *DB) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) { for _, entry := range dbEntries { adm, err := db.unmarshalAdmin(entry.Value, string(entry.Key)) if err != nil { - switch k := err.(type) { - case *admin.Error: - if k.IsType(admin.ErrorDeletedType) || k.IsType(admin.ErrorAuthorityMismatchType) { + var ae *admin.Error + if errors.As(err, &ae) { + if ae.IsType(admin.ErrorDeletedType) || ae.IsType(admin.ErrorAuthorityMismatchType) { continue } else { return nil, err } - default: + } else { return nil, err } } diff --git a/authority/admin/db/nosql/admin_test.go b/authority/admin/db/nosql/admin_test.go index 2631b68c..9961d7f5 100644 --- a/authority/admin/db/nosql/admin_test.go +++ b/authority/admin/db/nosql/admin_test.go @@ -68,16 +68,16 @@ func TestDB_getDBAdminBytes(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if b, err := d.getDBAdminBytes(context.Background(), adminID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -192,16 +192,16 @@ func TestDB_getDBAdmin(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if dba, err := d.getDBAdmin(context.Background(), adminID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -280,16 +280,16 @@ func TestDB_unmarshalDBAdmin(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{authorityID: admin.DefaultAuthorityID} if dba, err := d.unmarshalDBAdmin(tc.in, adminID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -355,16 +355,16 @@ func TestDB_unmarshalAdmin(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{authorityID: admin.DefaultAuthorityID} if adm, err := d.unmarshalAdmin(tc.in, adminID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -509,16 +509,16 @@ func TestDB_GetAdmin(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if adm, err := d.GetAdmin(context.Background(), adminID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -661,16 +661,16 @@ func TestDB_DeleteAdmin(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if err := d.DeleteAdmin(context.Background(), adminID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -812,16 +812,16 @@ func TestDB_UpdateAdmin(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if err := d.UpdateAdmin(context.Background(), tc.adm); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -910,16 +910,16 @@ func TestDB_CreateAdmin(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if err := d.CreateAdmin(context.Background(), tc.adm); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -1086,16 +1086,16 @@ func TestDB_GetAdmins(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if admins, err := d.GetAdmins(context.Background()); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/authority/admin/db/nosql/policy.go b/authority/admin/db/nosql/policy.go index d4f2e9f9..3023a3f6 100644 --- a/authority/admin/db/nosql/policy.go +++ b/authority/admin/db/nosql/policy.go @@ -83,6 +83,7 @@ func (db *DB) getDBAuthorityPolicyBytes(ctx context.Context, authorityID string) func (db *DB) unmarshalDBAuthorityPolicy(data []byte) (*dbAuthorityPolicy, error) { if len(data) == 0 { + //nolint:nilnil // legacy return nil, nil } var dba = new(dbAuthorityPolicy) @@ -102,6 +103,7 @@ func (db *DB) getDBAuthorityPolicy(ctx context.Context, authorityID string) (*db return nil, err } if dbap == nil { + //nolint:nilnil // legacy return nil, nil } if dbap.AuthorityID != authorityID { @@ -112,7 +114,6 @@ func (db *DB) getDBAuthorityPolicy(ctx context.Context, authorityID string) (*db } func (db *DB) CreateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { - dbap := &dbAuthorityPolicy{ ID: db.authorityID, AuthorityID: db.authorityID, @@ -228,7 +229,6 @@ func dbToLinked(p *dbPolicy) *linkedca.Policy { } func linkedToDB(p *linkedca.Policy) *dbPolicy { - if p == nil { return nil } diff --git a/authority/admin/db/nosql/policy_test.go b/authority/admin/db/nosql/policy_test.go index 3ffded6b..84f02a1d 100644 --- a/authority/admin/db/nosql/policy_test.go +++ b/authority/admin/db/nosql/policy_test.go @@ -72,16 +72,16 @@ func TestDB_getDBAuthorityPolicyBytes(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if b, err := d.getDBAuthorityPolicyBytes(tc.ctx, tc.authorityID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -208,16 +208,16 @@ func TestDB_getDBAuthorityPolicy(t *testing.T) { dbp, err := d.getDBAuthorityPolicy(tc.ctx, tc.authorityID) switch { case err != nil: - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -309,16 +309,16 @@ func TestDB_CreateAuthorityPolicy(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: tc.authorityID} if err := d.CreateAuthorityPolicy(tc.ctx, tc.policy); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -406,16 +406,16 @@ func TestDB_GetAuthorityPolicy(t *testing.T) { d := DB{db: tc.db, authorityID: tc.authorityID} got, err := d.GetAuthorityPolicy(tc.ctx) if err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -578,16 +578,16 @@ func TestDB_UpdateAuthorityPolicy(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: tc.authorityID} if err := d.UpdateAuthorityPolicy(tc.ctx, tc.policy); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -718,16 +718,16 @@ func TestDB_DeleteAuthorityPolicy(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: tc.authorityID} if err := d.DeleteAuthorityPolicy(tc.ctx); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/authority/admin/db/nosql/provisioner.go b/authority/admin/db/nosql/provisioner.go index 71d9c8d6..da116e0b 100644 --- a/authority/admin/db/nosql/provisioner.go +++ b/authority/admin/db/nosql/provisioner.go @@ -24,6 +24,24 @@ type dbProvisioner struct { SSHTemplate *linkedca.Template `json:"sshTemplate"` CreatedAt time.Time `json:"createdAt"` DeletedAt time.Time `json:"deletedAt"` + Webhooks []dbWebhook `json:"webhooks,omitempty"` +} + +type dbBasicAuth struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type dbWebhook struct { + Name string `json:"name"` + ID string `json:"id"` + URL string `json:"url"` + Kind string `json:"kind"` + Secret string `json:"secret"` + BearerToken string `json:"bearerToken,omitempty"` + BasicAuth *dbBasicAuth `json:"basicAuth,omitempty"` + DisableTLSClientAuth bool `json:"disableTLSClientAuth,omitempty"` + CertType string `json:"certType,omitempty"` } func (dbp *dbProvisioner) clone() *dbProvisioner { @@ -48,6 +66,7 @@ func (dbp *dbProvisioner) convert2linkedca() (*linkedca.Provisioner, error) { SshTemplate: dbp.SSHTemplate, CreatedAt: timestamppb.New(dbp.CreatedAt), DeletedAt: timestamppb.New(dbp.DeletedAt), + Webhooks: dbWebhooksToLinkedca(dbp.Webhooks), }, nil } @@ -122,14 +141,14 @@ func (db *DB) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, err for _, entry := range dbEntries { prov, err := db.unmarshalProvisioner(entry.Value, string(entry.Key)) if err != nil { - switch k := err.(type) { - case *admin.Error: - if k.IsType(admin.ErrorDeletedType) || k.IsType(admin.ErrorAuthorityMismatchType) { + var ae *admin.Error + if errors.As(err, &ae) { + if ae.IsType(admin.ErrorDeletedType) || ae.IsType(admin.ErrorAuthorityMismatchType) { continue } else { return nil, err } - default: + } else { return nil, err } } @@ -164,6 +183,7 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) X509Template: prov.X509Template, SSHTemplate: prov.SshTemplate, CreatedAt: clock.Now(), + Webhooks: linkedcaWebhooksToDB(prov.Webhooks), } if err := db.save(ctx, prov.Id, dbp, nil, "provisioner", provisionersTable); err != nil { @@ -193,6 +213,7 @@ func (db *DB) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) } nu.X509Template = prov.X509Template nu.SSHTemplate = prov.SshTemplate + nu.Webhooks = linkedcaWebhooksToDB(prov.Webhooks) return db.save(ctx, prov.Id, nu, old, "provisioner", provisionersTable) } @@ -209,3 +230,70 @@ func (db *DB) DeleteProvisioner(ctx context.Context, id string) error { return db.save(ctx, old.ID, nu, old, "provisioner", provisionersTable) } + +func dbWebhooksToLinkedca(dbwhs []dbWebhook) []*linkedca.Webhook { + if len(dbwhs) == 0 { + return nil + } + lwhs := make([]*linkedca.Webhook, len(dbwhs)) + + for i, dbwh := range dbwhs { + lwh := &linkedca.Webhook{ + Name: dbwh.Name, + Id: dbwh.ID, + Url: dbwh.URL, + Kind: linkedca.Webhook_Kind(linkedca.Webhook_Kind_value[dbwh.Kind]), + Secret: dbwh.Secret, + DisableTlsClientAuth: dbwh.DisableTLSClientAuth, + CertType: linkedca.Webhook_CertType(linkedca.Webhook_CertType_value[dbwh.CertType]), + } + if dbwh.BearerToken != "" { + lwh.Auth = &linkedca.Webhook_BearerToken{ + BearerToken: &linkedca.BearerToken{ + BearerToken: dbwh.BearerToken, + }, + } + } else if dbwh.BasicAuth != nil && (dbwh.BasicAuth.Username != "" || dbwh.BasicAuth.Password != "") { + lwh.Auth = &linkedca.Webhook_BasicAuth{ + BasicAuth: &linkedca.BasicAuth{ + Username: dbwh.BasicAuth.Username, + Password: dbwh.BasicAuth.Password, + }, + } + } + lwhs[i] = lwh + } + + return lwhs +} + +func linkedcaWebhooksToDB(lwhs []*linkedca.Webhook) []dbWebhook { + if len(lwhs) == 0 { + return nil + } + dbwhs := make([]dbWebhook, len(lwhs)) + + for i, lwh := range lwhs { + dbwh := dbWebhook{ + Name: lwh.Name, + ID: lwh.Id, + URL: lwh.Url, + Kind: lwh.Kind.String(), + Secret: lwh.Secret, + DisableTLSClientAuth: lwh.DisableTlsClientAuth, + CertType: lwh.CertType.String(), + } + switch a := lwh.GetAuth().(type) { + case *linkedca.Webhook_BearerToken: + dbwh.BearerToken = a.BearerToken.BearerToken + case *linkedca.Webhook_BasicAuth: + dbwh.BasicAuth = &dbBasicAuth{ + Username: a.BasicAuth.Username, + Password: a.BasicAuth.Password, + } + } + dbwhs[i] = dbwh + } + + return dbwhs +} diff --git a/authority/admin/db/nosql/provisioner_test.go b/authority/admin/db/nosql/provisioner_test.go index a399558a..8aa58d49 100644 --- a/authority/admin/db/nosql/provisioner_test.go +++ b/authority/admin/db/nosql/provisioner_test.go @@ -67,16 +67,16 @@ func TestDB_getDBProvisionerBytes(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if b, err := d.getDBProvisionerBytes(context.Background(), provID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -137,6 +137,7 @@ func TestDB_getDBProvisioner(t *testing.T) { } }, "fail/deleted": func(t *testing.T) test { + now := clock.Now() dbp := &dbProvisioner{ ID: provID, @@ -189,16 +190,16 @@ func TestDB_getDBProvisioner(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if dbp, err := d.getDBProvisioner(context.Background(), provID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -210,6 +211,7 @@ func TestDB_getDBProvisioner(t *testing.T) { assert.Equals(t, dbp.Name, tc.dbp.Name) assert.Equals(t, dbp.CreatedAt, tc.dbp.CreatedAt) assert.Fatal(t, dbp.DeletedAt.IsZero()) + assert.Equals(t, dbp.Webhooks, tc.dbp.Webhooks) } }) } @@ -275,16 +277,16 @@ func TestDB_unmarshalDBProvisioner(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{authorityID: admin.DefaultAuthorityID} if dbp, err := d.unmarshalDBProvisioner(tc.in, provID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -300,6 +302,7 @@ func TestDB_unmarshalDBProvisioner(t *testing.T) { assert.Equals(t, dbp.SSHTemplate, tc.dbp.SSHTemplate) assert.Equals(t, dbp.CreatedAt, tc.dbp.CreatedAt) assert.Fatal(t, dbp.DeletedAt.IsZero()) + assert.Equals(t, dbp.Webhooks, tc.dbp.Webhooks) } }) } @@ -353,6 +356,15 @@ func defaultDBP(t *testing.T) *dbProvisioner { Data: []byte("zap"), }, CreatedAt: clock.Now(), + Webhooks: []dbWebhook{ + { + Name: "metadata", + URL: "https://inventory.smallstep.com", + Kind: linkedca.Webhook_ENRICHING.String(), + Secret: "secret", + BearerToken: "token", + }, + }, } } @@ -397,16 +409,16 @@ func TestDB_unmarshalProvisioner(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{authorityID: admin.DefaultAuthorityID} if prov, err := d.unmarshalProvisioner(tc.in, provID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -419,6 +431,7 @@ func TestDB_unmarshalProvisioner(t *testing.T) { assert.Equals(t, prov.Claims, tc.dbp.Claims) assert.Equals(t, prov.X509Template, tc.dbp.X509Template) assert.Equals(t, prov.SshTemplate, tc.dbp.SSHTemplate) + assert.Equals(t, prov.Webhooks, dbWebhooksToLinkedca(tc.dbp.Webhooks)) retDetailsBytes, err := json.Marshal(prov.Details.GetData()) assert.FatalError(t, err) @@ -535,16 +548,16 @@ func TestDB_GetProvisioner(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if prov, err := d.GetProvisioner(context.Background(), provID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -557,6 +570,7 @@ func TestDB_GetProvisioner(t *testing.T) { assert.Equals(t, prov.Claims, tc.dbp.Claims) assert.Equals(t, prov.X509Template, tc.dbp.X509Template) assert.Equals(t, prov.SshTemplate, tc.dbp.SSHTemplate) + assert.Equals(t, prov.Webhooks, dbWebhooksToLinkedca(tc.dbp.Webhooks)) retDetailsBytes, err := json.Marshal(prov.Details.GetData()) assert.FatalError(t, err) @@ -629,6 +643,7 @@ func TestDB_DeleteProvisioner(t *testing.T) { assert.Equals(t, _dbp.SSHTemplate, dbp.SSHTemplate) assert.Equals(t, _dbp.CreatedAt, dbp.CreatedAt) assert.Equals(t, _dbp.Details, dbp.Details) + assert.Equals(t, _dbp.Webhooks, dbp.Webhooks) assert.True(t, _dbp.DeletedAt.Before(time.Now())) assert.True(t, _dbp.DeletedAt.After(time.Now().Add(-time.Minute))) @@ -668,6 +683,7 @@ func TestDB_DeleteProvisioner(t *testing.T) { assert.Equals(t, _dbp.SSHTemplate, dbp.SSHTemplate) assert.Equals(t, _dbp.CreatedAt, dbp.CreatedAt) assert.Equals(t, _dbp.Details, dbp.Details) + assert.Equals(t, _dbp.Webhooks, dbp.Webhooks) assert.True(t, _dbp.DeletedAt.Before(time.Now())) assert.True(t, _dbp.DeletedAt.After(time.Now().Add(-time.Minute))) @@ -683,16 +699,16 @@ func TestDB_DeleteProvisioner(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if err := d.DeleteProvisioner(context.Background(), provID); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -819,6 +835,7 @@ func TestDB_GetProvisioners(t *testing.T) { assert.Equals(t, provs[0].Claims, fooProv.Claims) assert.Equals(t, provs[0].X509Template, fooProv.X509Template) assert.Equals(t, provs[0].SshTemplate, fooProv.SSHTemplate) + assert.Equals(t, provs[0].Webhooks, dbWebhooksToLinkedca(fooProv.Webhooks)) retDetailsBytes, err := json.Marshal(provs[0].Details.GetData()) assert.FatalError(t, err) @@ -831,6 +848,7 @@ func TestDB_GetProvisioners(t *testing.T) { assert.Equals(t, provs[1].Claims, zapProv.Claims) assert.Equals(t, provs[1].X509Template, zapProv.X509Template) assert.Equals(t, provs[1].SshTemplate, zapProv.SSHTemplate) + assert.Equals(t, provs[1].Webhooks, dbWebhooksToLinkedca(zapProv.Webhooks)) retDetailsBytes, err = json.Marshal(provs[1].Details.GetData()) assert.FatalError(t, err) @@ -844,16 +862,16 @@ func TestDB_GetProvisioners(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if provs, err := d.GetProvisioners(context.Background()); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -895,6 +913,7 @@ func TestDB_CreateProvisioner(t *testing.T) { assert.Equals(t, _dbp.Claims, prov.Claims) assert.Equals(t, _dbp.X509Template, prov.X509Template) assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate) + assert.Equals(t, _dbp.Webhooks, linkedcaWebhooksToDB(prov.Webhooks)) retDetailsBytes, err := json.Marshal(prov.Details.GetData()) assert.FatalError(t, err) @@ -932,6 +951,7 @@ func TestDB_CreateProvisioner(t *testing.T) { assert.Equals(t, _dbp.Claims, prov.Claims) assert.Equals(t, _dbp.X509Template, prov.X509Template) assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate) + assert.Equals(t, _dbp.Webhooks, linkedcaWebhooksToDB(prov.Webhooks)) retDetailsBytes, err := json.Marshal(prov.Details.GetData()) assert.FatalError(t, err) @@ -952,16 +972,16 @@ func TestDB_CreateProvisioner(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if err := d.CreateProvisioner(context.Background(), tc.prov); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -1080,6 +1100,7 @@ func TestDB_UpdateProvisioner(t *testing.T) { assert.Equals(t, _dbp.Claims, prov.Claims) assert.Equals(t, _dbp.X509Template, prov.X509Template) assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate) + assert.Equals(t, _dbp.Webhooks, linkedcaWebhooksToDB(prov.Webhooks)) retDetailsBytes, err := json.Marshal(prov.Details.GetData()) assert.FatalError(t, err) @@ -1141,6 +1162,12 @@ func TestDB_UpdateProvisioner(t *testing.T) { }, }, } + prov.Webhooks = []*linkedca.Webhook{ + { + Name: "users", + Url: "https://example.com/users", + }, + } data, err := json.Marshal(dbp) assert.FatalError(t, err) @@ -1168,6 +1195,7 @@ func TestDB_UpdateProvisioner(t *testing.T) { assert.Equals(t, _dbp.Claims, prov.Claims) assert.Equals(t, _dbp.X509Template, prov.X509Template) assert.Equals(t, _dbp.SSHTemplate, prov.SshTemplate) + assert.Equals(t, _dbp.Webhooks, linkedcaWebhooksToDB(prov.Webhooks)) retDetailsBytes, err := json.Marshal(prov.Details.GetData()) assert.FatalError(t, err) @@ -1188,16 +1216,16 @@ func TestDB_UpdateProvisioner(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: admin.DefaultAuthorityID} if err := d.UpdateProvisioner(context.Background(), tc.prov); err != nil { - switch k := err.(type) { - case *admin.Error: + var ae *admin.Error + if errors.As(err, &ae) { if assert.NotNil(t, tc.adminErr) { - assert.Equals(t, k.Type, tc.adminErr.Type) - assert.Equals(t, k.Detail, tc.adminErr.Detail) - assert.Equals(t, k.Status, tc.adminErr.Status) - assert.Equals(t, k.Err.Error(), tc.adminErr.Err.Error()) - assert.Equals(t, k.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Type, tc.adminErr.Type) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) + assert.Equals(t, ae.Status, tc.adminErr.Status) + assert.Equals(t, ae.Err.Error(), tc.adminErr.Err.Error()) + assert.Equals(t, ae.Detail, tc.adminErr.Detail) } - default: + } else { if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -1206,3 +1234,164 @@ func TestDB_UpdateProvisioner(t *testing.T) { }) } } + +func Test_linkedcaWebhooksToDB(t *testing.T) { + type test struct { + in []*linkedca.Webhook + want []dbWebhook + } + var tests = map[string]test{ + "nil": { + in: nil, + want: nil, + }, + "zero": { + in: []*linkedca.Webhook{}, + want: nil, + }, + "bearer": { + in: []*linkedca.Webhook{ + { + Name: "bearer", + Url: "https://example.com", + Kind: linkedca.Webhook_ENRICHING, + Secret: "secret", + Auth: &linkedca.Webhook_BearerToken{ + BearerToken: &linkedca.BearerToken{ + BearerToken: "token", + }, + }, + DisableTlsClientAuth: true, + CertType: linkedca.Webhook_X509, + }, + }, + want: []dbWebhook{ + { + Name: "bearer", + URL: "https://example.com", + Kind: "ENRICHING", + Secret: "secret", + BearerToken: "token", + DisableTLSClientAuth: true, + CertType: linkedca.Webhook_X509.String(), + }, + }, + }, + "basic": { + in: []*linkedca.Webhook{ + { + Name: "basic", + Url: "https://example.com", + Kind: linkedca.Webhook_ENRICHING, + Secret: "secret", + Auth: &linkedca.Webhook_BasicAuth{ + BasicAuth: &linkedca.BasicAuth{ + Username: "user", + Password: "pass", + }, + }, + }, + }, + want: []dbWebhook{ + { + Name: "basic", + URL: "https://example.com", + Kind: "ENRICHING", + Secret: "secret", + BasicAuth: &dbBasicAuth{ + Username: "user", + Password: "pass", + }, + CertType: linkedca.Webhook_ALL.String(), + }, + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := linkedcaWebhooksToDB(tc.in) + assert.Equals(t, tc.want, got) + }) + } +} + +func Test_dbWebhooksToLinkedca(t *testing.T) { + type test struct { + in []dbWebhook + want []*linkedca.Webhook + } + var tests = map[string]test{ + "nil": { + in: nil, + want: nil, + }, + "zero": { + in: []dbWebhook{}, + want: nil, + }, + "bearer": { + in: []dbWebhook{ + { + Name: "bearer", + ID: "69350cb6-6c31-4b5e-bf25-affd5053427d", + URL: "https://example.com", + Kind: "ENRICHING", + Secret: "secret", + BearerToken: "token", + DisableTLSClientAuth: true, + }, + }, + want: []*linkedca.Webhook{ + { + Name: "bearer", + Id: "69350cb6-6c31-4b5e-bf25-affd5053427d", + Url: "https://example.com", + Kind: linkedca.Webhook_ENRICHING, + Secret: "secret", + Auth: &linkedca.Webhook_BearerToken{ + BearerToken: &linkedca.BearerToken{ + BearerToken: "token", + }, + }, + DisableTlsClientAuth: true, + }, + }, + }, + "basic": { + in: []dbWebhook{ + { + Name: "basic", + ID: "69350cb6-6c31-4b5e-bf25-affd5053427d", + URL: "https://example.com", + Kind: "ENRICHING", + Secret: "secret", + BasicAuth: &dbBasicAuth{ + Username: "user", + Password: "pass", + }, + }, + }, + want: []*linkedca.Webhook{ + { + Name: "basic", + Id: "69350cb6-6c31-4b5e-bf25-affd5053427d", + Url: "https://example.com", + Kind: linkedca.Webhook_ENRICHING, + Secret: "secret", + Auth: &linkedca.Webhook_BasicAuth{ + BasicAuth: &linkedca.BasicAuth{ + Username: "user", + Password: "pass", + }, + }, + }, + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := dbWebhooksToLinkedca(tc.in) + assert.Equals(t, tc.want, got) + }) + } +} diff --git a/authority/admin/errors.go b/authority/admin/errors.go index 2cf0c0e5..c729c8b2 100644 --- a/authority/admin/errors.go +++ b/authority/admin/errors.go @@ -156,16 +156,17 @@ func NewErrorISE(msg string, args ...interface{}) *Error { // WrapError attempts to wrap the internal error. func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Error { - switch e := err.(type) { - case nil: + var ee *Error + switch { + case err == nil: return nil - case *Error: - if e.Err == nil { - e.Err = errors.Errorf(msg+"; "+e.Detail, args...) + case errors.As(err, &ee): + if ee.Err == nil { + ee.Err = errors.Errorf(msg+"; "+ee.Detail, args...) } else { - e.Err = errors.Wrapf(e.Err, msg, args...) + ee.Err = errors.Wrapf(ee.Err, msg, args...) } - return e + return ee default: return newError(typ, errors.Wrapf(err, msg, args...)) } diff --git a/authority/authority.go b/authority/authority.go index 73aa9cca..3fb6f51f 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -1,12 +1,14 @@ package authority import ( + "bytes" "context" "crypto" "crypto/sha256" "crypto/x509" "encoding/hex" "log" + "net/http" "strings" "sync" "time" @@ -24,6 +26,7 @@ import ( adminDBNosql "github.com/smallstep/certificates/authority/admin/db/nosql" "github.com/smallstep/certificates/authority/administrator" "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/internal/constraints" "github.com/smallstep/certificates/authority/policy" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/cas" @@ -44,16 +47,18 @@ type Authority struct { adminDB admin.DB templates *templates.Templates linkedCAToken string + webhookClient *http.Client // X509 CA - password []byte - issuerPassword []byte - x509CAService cas.CertificateAuthorityService - rootX509Certs []*x509.Certificate - rootX509CertPool *x509.CertPool - federatedX509Certs []*x509.Certificate - certificates *sync.Map - x509Enforcers []provisioner.CertificateEnforcer + password []byte + issuerPassword []byte + x509CAService cas.CertificateAuthorityService + rootX509Certs []*x509.Certificate + rootX509CertPool *x509.CertPool + federatedX509Certs []*x509.Certificate + intermediateX509Certs []*x509.Certificate + certificates *sync.Map + x509Enforcers []provisioner.CertificateEnforcer // SCEP CA scepService *scep.Service @@ -68,7 +73,12 @@ type Authority struct { sshCAUserFederatedCerts []ssh.PublicKey sshCAHostFederatedCerts []ssh.PublicKey - // Do not re-initialize + // CRL vars + crlTicker *time.Ticker + crlStopper chan struct{} + crlMutex sync.Mutex + + // If true, do not re-initialize initOnce bool startTime time.Time @@ -80,13 +90,17 @@ type Authority struct { authorizeRenewFunc provisioner.AuthorizeRenewFunc authorizeSSHRenewFunc provisioner.AuthorizeSSHRenewFunc - // Policy engines - policyEngine *policy.Engine + // Constraints and Policy engines + constraintsEngine *constraints.Engine + policyEngine *policy.Engine adminMutex sync.RWMutex - // Do Not initialize the authority + // If true, do not initialize the authority skipInit bool + + // If true, do not output initialization logs + quietInit bool } // Info contains information about the authority. @@ -368,11 +382,17 @@ func (a *Authority) init() error { } options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ SigningKey: a.config.IntermediateKey, - Password: []byte(a.password), + Password: a.password, }) if err != nil { return err } + // If not defined with an option, add intermediates to the list of + // certificates used for name constraints validation at issuance + // time. + if len(a.intermediateX509Certs) == 0 { + a.intermediateX509Certs = append(a.intermediateX509Certs, options.CertificateChain...) + } } a.x509CAService, err = cas.New(ctx, options) if err != nil { @@ -434,7 +454,7 @@ func (a *Authority) init() error { if a.config.SSH.HostKey != "" { signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ SigningKey: a.config.SSH.HostKey, - Password: []byte(a.sshHostPassword), + Password: a.sshHostPassword, }) if err != nil { return err @@ -460,7 +480,7 @@ func (a *Authority) init() error { if a.config.SSH.UserKey != "" { signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ SigningKey: a.config.SSH.UserKey, - Password: []byte(a.sshUserPassword), + Password: a.sshUserPassword, }) if err != nil { return err @@ -545,7 +565,7 @@ func (a *Authority) init() error { options.CertificateChain = append(options.CertificateChain, a.rootX509Certs...) options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ SigningKey: a.config.IntermediateKey, - Password: []byte(a.password), + Password: a.password, }) if err != nil { return err @@ -554,7 +574,7 @@ func (a *Authority) init() error { if km, ok := a.keyManager.(kmsapi.Decrypter); ok { options.Decrypter, err = km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ DecryptionKey: a.config.IntermediateKey, - Password: []byte(a.password), + Password: a.password, }) if err != nil { return err @@ -588,20 +608,74 @@ func (a *Authority) init() error { return admin.WrapErrorISE(err, "error loading provisioners to initialize authority") } if len(provs) == 0 && !strings.EqualFold(a.config.AuthorityConfig.DeploymentType, "linked") { - // Create First Provisioner - prov, err := CreateFirstProvisioner(ctx, a.adminDB, string(a.password)) - if err != nil { - return admin.WrapErrorISE(err, "error creating first provisioner") + // Migration will currently only be kicked off once, because either one or more provisioners + // are migrated or a default JWK provisioner will be created in the DB. It won't run for + // linked or hosted deployments. Not for linked, because that case is explicitly checked + // for above. Not for hosted, because there'll be at least an existing OIDC provisioner. + var firstJWKProvisioner *linkedca.Provisioner + if len(a.config.AuthorityConfig.Provisioners) > 0 { + // Existing provisioners detected; try migrating them to DB storage. + a.initLogf("Starting migration of provisioners") + for _, p := range a.config.AuthorityConfig.Provisioners { + lp, err := ProvisionerToLinkedca(p) + if err != nil { + return admin.WrapErrorISE(err, "error transforming provisioner %q while migrating", p.GetName()) + } + + // Store the provisioner to be migrated + if err := a.adminDB.CreateProvisioner(ctx, lp); err != nil { + return admin.WrapErrorISE(err, "error creating provisioner %q while migrating", p.GetName()) + } + + // Mark the first JWK provisioner, so that it can be used for administration purposes + if firstJWKProvisioner == nil && lp.Type == linkedca.Provisioner_JWK { + firstJWKProvisioner = lp + a.initLogf("Migrated JWK provisioner %q with admin permissions", p.GetName()) + } else { + a.initLogf("Migrated %s provisioner %q", p.GetType(), p.GetName()) + } + } + + c := a.config + if c.WasLoadedFromFile() { + // The provisioners in the configuration file can be deleted from + // the file by editing it. Automatic rewriting of the file was considered + // to be too surprising for users and not the right solution for all + // use cases, so we leave it up to users to this themselves. + a.initLogf("Provisioners that were migrated can now be removed from `ca.json` by editing it") + } + + a.initLogf("Finished migrating provisioners") + } + + // Create first JWK provisioner for remote administration purposes if none exists yet + if firstJWKProvisioner == nil { + firstJWKProvisioner, err = CreateFirstProvisioner(ctx, a.adminDB, string(a.password)) + if err != nil { + return admin.WrapErrorISE(err, "error creating first provisioner") + } + a.initLogf("Created JWK provisioner %q with admin permissions", firstJWKProvisioner.GetName()) } - // Create first admin + // Create first super admin, belonging to the first JWK provisioner + // TODO(hs): pass a user-provided first super admin subject to here. With `ca init` it's + // added to the DB immediately if using remote management. But when migrating from + // ca.json to the DB, this option doesn't exist. Adding a flag just to do it during + // migration isn't nice. We could opt for a user to change it afterwards. There exist + // cases in which creation of `step` could lock out a user from API access. This is the + // case if `step` isn't allowed to be signed by Name Constraints or the X.509 policy. + // We have protection for that when creating and updating a policy, but if a policy or + // Name Constraints are in use at the time of migration, that could lock the user out. + superAdminSubject := "step" if err := a.adminDB.CreateAdmin(ctx, &linkedca.Admin{ - ProvisionerId: prov.Id, - Subject: "step", + ProvisionerId: firstJWKProvisioner.Id, + Subject: superAdminSubject, Type: linkedca.Admin_SUPER_ADMIN, }); err != nil { return admin.WrapErrorISE(err, "error creating first admin") } + + a.initLogf("Created super admin %q for JWK provisioner %q", superAdminSubject, firstJWKProvisioner.GetName()) } } @@ -610,6 +684,21 @@ func (a *Authority) init() error { return err } + // Load X509 constraints engine. + // + // This is currently only available in CA mode. + if size := len(a.intermediateX509Certs); size > 0 { + last := a.intermediateX509Certs[size-1] + constraintCerts := make([]*x509.Certificate, 0, size+1) + constraintCerts = append(constraintCerts, a.intermediateX509Certs...) + for _, root := range a.rootX509Certs { + if bytes.Equal(last.RawIssuer, root.RawSubject) && bytes.Equal(last.AuthorityKeyId, root.SubjectKeyId) { + constraintCerts = append(constraintCerts, root) + } + } + a.constraintsEngine = constraints.New(constraintCerts...) + } + // Load x509 and SSH Policy Engines if err := a.reloadPolicyEngines(ctx); err != nil { return err @@ -627,6 +716,18 @@ func (a *Authority) init() error { a.templates.Data["Step"] = tmplVars } + // Start the CRL generator, we can assume the configuration is validated. + if a.config.CRL.IsEnabled() { + // Default cache duration to the default one + if v := a.config.CRL.CacheDuration; v == nil || v.Duration <= 0 { + a.config.CRL.CacheDuration = config.DefaultCRLCacheDuration + } + // Start CRL generator + if err := a.startCRLGenerator(); err != nil { + return err + } + } + // JWT numeric dates are seconds. a.startTime = time.Now().Truncate(time.Second) // Set flag indicating that initialization has been completed, and should @@ -636,6 +737,14 @@ func (a *Authority) init() error { return nil } +// initLogf is used to log initialization information. The output +// can be disabled by starting the CA with the `--quiet` flag. +func (a *Authority) initLogf(format string, v ...any) { + if !a.quietInit { + log.Printf(format, v...) + } +} + // GetID returns the define authority id or a zero uuid. func (a *Authority) GetID() string { const zeroUUID = "00000000-0000-0000-0000-000000000000" @@ -685,6 +794,11 @@ func (a *Authority) IsAdminAPIEnabled() bool { // Shutdown safely shuts down any clients, databases, etc. held by the Authority. func (a *Authority) Shutdown() error { + if a.crlTicker != nil { + a.crlTicker.Stop() + close(a.crlStopper) + } + if err := a.keyManager.Close(); err != nil { log.Printf("error closing the key manager: %v", err) } @@ -693,6 +807,11 @@ func (a *Authority) Shutdown() error { // CloseForReload closes internal services, to allow a safe reload. func (a *Authority) CloseForReload() { + if a.crlTicker != nil { + a.crlTicker.Stop() + close(a.crlStopper) + } + if err := a.keyManager.Close(); err != nil { log.Printf("error closing the key manager: %v", err) } @@ -733,11 +852,49 @@ func (a *Authority) requiresSCEPService() bool { return false } -// GetSCEPService returns the configured SCEP Service -// TODO: this function is intended to exist temporarily -// in order to make SCEP work more easily. It can be -// made more correct by using the right interfaces/abstractions -// after it works as expected. +// GetSCEPService returns the configured SCEP Service. +// +// TODO: this function is intended to exist temporarily in order to make SCEP +// work more easily. It can be made more correct by using the right +// interfaces/abstractions after it works as expected. func (a *Authority) GetSCEPService() *scep.Service { return a.scepService } + +func (a *Authority) startCRLGenerator() error { + if !a.config.CRL.IsEnabled() { + return nil + } + + // Check that there is a valid CRL in the DB right now. If it doesn't exist + // or is expired, generate one now + _, ok := a.db.(db.CertificateRevocationListDB) + if !ok { + return errors.Errorf("CRL Generation requested, but database does not support CRL generation") + } + + // Always create a new CRL on startup in case the CA has been down and the + // time to next expected CRL update is less than the cache duration. + if err := a.GenerateCertificateRevocationList(); err != nil { + return errors.Wrap(err, "could not generate a CRL") + } + + a.crlStopper = make(chan struct{}, 1) + a.crlTicker = time.NewTicker(a.config.CRL.TickerDuration()) + + go func() { + for { + select { + case <-a.crlTicker.C: + log.Println("Regenerating CRL") + if err := a.GenerateCertificateRevocationList(); err != nil { + log.Printf("error regenerating the CRL: %v", err) + } + case <-a.crlStopper: + return + } + } + }() + + return nil +} diff --git a/authority/authorize.go b/authority/authorize.go index 8f916e1d..1e50da89 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/pkg/errors" "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" @@ -285,7 +286,7 @@ func (a *Authority) authorizeRevoke(ctx context.Context, token string) error { // extra extension cannot be found, authorize the renewal by default. // // TODO(mariano): should we authorize by default? -func (a *Authority) authorizeRenew(cert *x509.Certificate) error { +func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) error { serial := cert.SerialNumber.String() var opts = []interface{}{errs.WithKeyVal("serialNumber", serial)} @@ -307,7 +308,7 @@ func (a *Authority) authorizeRenew(cert *x509.Certificate) error { return errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...) } } - if err := p.AuthorizeRenew(context.Background(), cert); err != nil { + if err := p.AuthorizeRenew(ctx, cert); err != nil { return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) } return nil @@ -416,16 +417,16 @@ func (a *Authority) AuthorizeRenewToken(ctx context.Context, ott string) (*x509. Subject: leaf.Subject.CommonName, Time: time.Now().UTC(), }, time.Minute); err != nil { - switch err { - case jose.ErrInvalidIssuer: + switch { + case errors.Is(err, jose.ErrInvalidIssuer): return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: invalid issuer claim (iss)")) - case jose.ErrInvalidSubject: + case errors.Is(err, jose.ErrInvalidSubject): return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: invalid subject claim (sub)")) - case jose.ErrNotValidYet: + case errors.Is(err, jose.ErrNotValidYet): return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: token not valid yet (nbf)")) - case jose.ErrExpired: + case errors.Is(err, jose.ErrExpired): return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: token is expired (exp)")) - case jose.ErrIssuedInTheFuture: + case errors.Is(err, jose.ErrIssuedInTheFuture): return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: token issued in the future (iat)")) default: return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token")) @@ -433,7 +434,7 @@ func (a *Authority) AuthorizeRenewToken(ctx context.Context, ott string) (*x509. } audiences := a.config.GetAudiences().Renew - if !matchesAudience(claims.Audience, audiences) { + if !matchesAudience(claims.Audience, audiences) && !isRAProvisioner(p) { return nil, errs.InternalServerErr(jose.ErrInvalidAudience, errs.WithMessage("error validating renew token: invalid audience claim (aud)")) } diff --git a/authority/authorize_test.go b/authority/authorize_test.go index af80d3d3..bec34fd6 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -313,8 +313,8 @@ func TestAuthority_authorizeToken(t *testing.T) { p, err := tc.auth.authorizeToken(context.Background(), tc.token) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -399,8 +399,8 @@ func TestAuthority_authorizeRevoke(t *testing.T) { if err := tc.auth.authorizeRevoke(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -484,14 +484,14 @@ func TestAuthority_authorizeSign(t *testing.T) { got, err := tc.auth.authorizeSign(context.Background(), tc.token) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { if assert.Nil(t, tc.err) { - assert.Equals(t, 9, len(got)) // number of provisioner.SignOptions returned + assert.Equals(t, 10, len(got)) // number of provisioner.SignOptions returned } } }) @@ -743,13 +743,13 @@ func TestAuthority_Authorize(t *testing.T) { if err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, got) - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) - ctxErr, ok := err.(*errs.Error) - assert.Fatal(t, ok, "error is not of type *errs.Error") + var ctxErr *errs.Error + assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") assert.Equals(t, ctxErr.Details["token"], tc.token) } } else { @@ -876,16 +876,16 @@ func TestAuthority_authorizeRenew(t *testing.T) { t.Run(name, func(t *testing.T) { tc := genTestCase(t) - err := tc.auth.authorizeRenew(tc.cert) + err := tc.auth.authorizeRenew(context.Background(), tc.cert) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCoder interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) - ctxErr, ok := err.(*errs.Error) - assert.Fatal(t, ok, "error is not of type *errs.Error") + var ctxErr *errs.Error + assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) } } else { @@ -1027,14 +1027,14 @@ func TestAuthority_authorizeSSHSign(t *testing.T) { got, err := tc.auth.authorizeSSHSign(context.Background(), tc.token) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { if assert.Nil(t, tc.err) { - assert.Len(t, 9, got) // number of provisioner.SignOptions returned + assert.Len(t, 10, got) // number of provisioner.SignOptions returned } } }) @@ -1144,8 +1144,8 @@ func TestAuthority_authorizeSSHRenew(t *testing.T) { got, err := tc.auth.authorizeSSHRenew(context.Background(), tc.token) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -1244,8 +1244,8 @@ func TestAuthority_authorizeSSHRevoke(t *testing.T) { if err := tc.auth.authorizeSSHRevoke(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -1337,8 +1337,8 @@ func TestAuthority_authorizeSSHRekey(t *testing.T) { cert, signOpts, err := tc.auth.authorizeSSHRekey(context.Background(), tc.token) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -1459,6 +1459,37 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) { }) return nil })) + a4 := testAuthority(t) + a4.db = &db.MockAuthDB{ + MUseToken: func(id, tok string) (bool, error) { + return true, nil + }, + MGetCertificateData: func(serialNumber string) (*db.CertificateData, error) { + return &db.CertificateData{ + Provisioner: &db.ProvisionerData{ID: "Max:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk", Name: "Max"}, + RaInfo: &provisioner.RAInfo{ProvisionerName: "ra"}, + }, nil + }, + } + t4, c4 := generateX5cToken(a1, signer, jose.Claims{ + Audience: []string{"https://ra.example.com/1.0/renew"}, + Subject: "test.example.com", + Issuer: "step-ca-client/1.0", + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)), + }, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error { + cert.NotBefore = now + cert.NotAfter = now.Add(time.Hour) + b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil}) + if err != nil { + return err + } + cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1}, + Value: b, + }) + return nil + })) badSigner, _ := generateX5cToken(a1, otherSigner, jose.Claims{ Audience: []string{"https://example.com/1.0/renew"}, Subject: "test.example.com", @@ -1627,6 +1658,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) { {"ok", a1, args{ctx, t1}, c1, false}, {"ok expired cert", a1, args{ctx, t2}, c2, false}, {"ok provisioner issuer", a1, args{ctx, t3}, c3, false}, + {"ok ra provisioner", a4, args{ctx, t4}, c4, false}, {"fail token", a1, args{ctx, "not.a.token"}, nil, true}, {"fail token reuse", a1, args{ctx, t1}, nil, true}, {"fail token signature", a1, args{ctx, badSigner}, nil, true}, diff --git a/authority/config/config.go b/authority/config/config.go index c5e74b39..f58a3354 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -35,8 +35,13 @@ var ( // DefaultEnableSSHCA enable SSH CA features per provisioner or globally // for all provisioners. DefaultEnableSSHCA = false - // GlobalProvisionerClaims default claims for the Authority. Can be overridden - // by provisioner specific claims. + // DefaultCRLCacheDuration is the default cache duration for the CRL. + DefaultCRLCacheDuration = &provisioner.Duration{Duration: 24 * time.Hour} + // DefaultCRLExpiredDuration is the default duration in which expired + // certificates will remain in the CRL after expiration. + DefaultCRLExpiredDuration = time.Hour + // GlobalProvisionerClaims is the default duration that expired certificates + // remain in the CRL after expiration. GlobalProvisionerClaims = provisioner.Claims{ MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, // TLS certs MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour}, @@ -72,7 +77,60 @@ type Config struct { Password string `json:"password,omitempty"` Templates *templates.Templates `json:"templates,omitempty"` CommonName string `json:"commonName,omitempty"` + CRL *CRLConfig `json:"crl,omitempty"` SkipValidation bool `json:"-"` + + // Keeps record of the filename the Config is read from + loadedFromFilepath string +} + +// CRLConfig represents config options for CRL generation +type CRLConfig struct { + Enabled bool `json:"enabled"` + GenerateOnRevoke bool `json:"generateOnRevoke,omitempty"` + CacheDuration *provisioner.Duration `json:"cacheDuration,omitempty"` + RenewPeriod *provisioner.Duration `json:"renewPeriod,omitempty"` +} + +// IsEnabled returns if the CRL is enabled. +func (c *CRLConfig) IsEnabled() bool { + return c != nil && c.Enabled +} + +// Validate validates the CRL configuration. +func (c *CRLConfig) Validate() error { + if c == nil { + return nil + } + + if c.CacheDuration != nil && c.CacheDuration.Duration < 0 { + return errors.New("crl.cacheDuration must be greater than or equal to 0") + } + + if c.RenewPeriod != nil && c.RenewPeriod.Duration < 0 { + return errors.New("crl.renewPeriod must be greater than or equal to 0") + } + + if c.RenewPeriod != nil && c.CacheDuration != nil && + c.RenewPeriod.Duration > c.CacheDuration.Duration { + return errors.New("crl.cacheDuration must be greater than or equal to crl.renewPeriod") + } + + return nil +} + +// TickerDuration the renewal ticker duration. This is set by renewPeriod, of it +// is not set is ~2/3 of cacheDuration. +func (c *CRLConfig) TickerDuration() time.Duration { + if !c.IsEnabled() { + return 0 + } + + if c.RenewPeriod != nil && c.RenewPeriod.Duration > 0 { + return c.RenewPeriod.Duration + } + + return (c.CacheDuration.Duration / 3) * 2 } // ASN1DN contains ASN1.DN attributes that are used in Subject and Issuer @@ -163,6 +221,10 @@ func LoadConfiguration(filename string) (*Config, error) { return nil, errors.Wrapf(err, "error parsing %s", filename) } + // store filename that was read to populate Config + c.loadedFromFilepath = filename + + // initialize the Config c.Init() return &c, nil @@ -183,6 +245,9 @@ func (c *Config) Init() { if c.CommonName == "" { c.CommonName = "Step Online CA" } + if c.CRL != nil && c.CRL.Enabled && c.CRL.CacheDuration == nil { + c.CRL.CacheDuration = DefaultCRLCacheDuration + } c.AuthorityConfig.init() } @@ -199,6 +264,30 @@ func (c *Config) Save(filename string) error { return errors.Wrapf(enc.Encode(c), "error writing %s", filename) } +// Commit saves the current configuration to the same +// file it was initially loaded from. +// +// TODO(hs): rename Save() to WriteTo() and replace this +// with Save()? Or is Commit clear enough. +func (c *Config) Commit() error { + if !c.WasLoadedFromFile() { + return errors.New("cannot commit configuration if not loaded from file") + } + return c.Save(c.loadedFromFilepath) +} + +// WasLoadedFromFile returns whether or not the Config was +// loaded from a file. +func (c *Config) WasLoadedFromFile() bool { + return c.loadedFromFilepath != "" +} + +// Filepath returns the path to the file the Config was +// loaded from. +func (c *Config) Filepath() string { + return c.loadedFromFilepath +} + // Validate validates the configuration. func (c *Config) Validate() error { switch { @@ -269,6 +358,11 @@ func (c *Config) Validate() error { return err } + // Validate crl config: nil is ok + if err := c.CRL.Validate(); err != nil { + return err + } + return c.AuthorityConfig.Validate(c.GetAudiences()) } diff --git a/authority/config/tls_options.go b/authority/config/tls_options.go index 01ab3d0a..5ef6c894 100644 --- a/authority/config/tls_options.go +++ b/authority/config/tls_options.go @@ -169,7 +169,7 @@ func (t *TLSOptions) TLSConfig() *tls.Config { rs = tls.RenegotiateNever } - // nolint:gosec // default MinVersion 1.2, if defined but empty 1.3 is used + //nolint:gosec // default MinVersion 1.2, if defined but empty 1.3 is used return &tls.Config{ CipherSuites: t.CipherSuites.Value(), MinVersion: t.MinVersion.Value(), diff --git a/authority/internal/constraints/constraints.go b/authority/internal/constraints/constraints.go new file mode 100644 index 00000000..a1cbde7e --- /dev/null +++ b/authority/internal/constraints/constraints.go @@ -0,0 +1,135 @@ +package constraints + +import ( + "crypto/x509" + "fmt" + "net" + "net/http" + "net/url" + + "github.com/smallstep/certificates/errs" +) + +// ConstraintError is the typed error that will be returned if a constraint +// error is found. +type ConstraintError struct { + Type string + Name string + Detail string +} + +// Error implements the error interface. +func (e ConstraintError) Error() string { + return e.Detail +} + +// As implements the As(any) bool interface and allows to use "errors.As()" to +// convert the ConstraintError to an errs.Error. +func (e ConstraintError) As(v any) bool { + if err, ok := v.(**errs.Error); ok { + *err = &errs.Error{ + Status: http.StatusForbidden, + Msg: e.Detail, + Err: e, + } + return true + } + return false +} + +// Engine implements a constraint validator for DNS names, IP addresses, Email +// addresses and URIs. +type Engine struct { + hasNameConstraints bool + permittedDNSDomains []string + excludedDNSDomains []string + permittedIPRanges []*net.IPNet + excludedIPRanges []*net.IPNet + permittedEmailAddresses []string + excludedEmailAddresses []string + permittedURIDomains []string + excludedURIDomains []string +} + +// New creates a constraint validation engine that contains the given chain of +// certificates. +func New(chain ...*x509.Certificate) *Engine { + e := new(Engine) + for _, crt := range chain { + e.permittedDNSDomains = append(e.permittedDNSDomains, crt.PermittedDNSDomains...) + e.excludedDNSDomains = append(e.excludedDNSDomains, crt.ExcludedDNSDomains...) + e.permittedIPRanges = append(e.permittedIPRanges, crt.PermittedIPRanges...) + e.excludedIPRanges = append(e.excludedIPRanges, crt.ExcludedIPRanges...) + e.permittedEmailAddresses = append(e.permittedEmailAddresses, crt.PermittedEmailAddresses...) + e.excludedEmailAddresses = append(e.excludedEmailAddresses, crt.ExcludedEmailAddresses...) + e.permittedURIDomains = append(e.permittedURIDomains, crt.PermittedURIDomains...) + e.excludedURIDomains = append(e.excludedURIDomains, crt.ExcludedURIDomains...) + } + + e.hasNameConstraints = len(e.permittedDNSDomains) > 0 || len(e.excludedDNSDomains) > 0 || + len(e.permittedIPRanges) > 0 || len(e.excludedIPRanges) > 0 || + len(e.permittedEmailAddresses) > 0 || len(e.excludedEmailAddresses) > 0 || + len(e.permittedURIDomains) > 0 || len(e.excludedURIDomains) > 0 + + return e +} + +// Validate checks the given names with the name constraints defined in the +// service. +func (e *Engine) Validate(dnsNames []string, ipAddresses []net.IP, emailAddresses []string, uris []*url.URL) error { + if e == nil || !e.hasNameConstraints { + return nil + } + + for _, name := range dnsNames { + if err := checkNameConstraints("DNS name", name, name, e.permittedDNSDomains, e.excludedDNSDomains, + func(parsedName, constraint any) (bool, error) { + return matchDomainConstraint(parsedName.(string), constraint.(string)) + }, + ); err != nil { + return err + } + } + + for _, ip := range ipAddresses { + if err := checkNameConstraints("IP address", ip.String(), ip, e.permittedIPRanges, e.excludedIPRanges, + func(parsedName, constraint any) (bool, error) { + return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) + }, + ); err != nil { + return err + } + } + + for _, email := range emailAddresses { + mailbox, ok := parseRFC2821Mailbox(email) + if !ok { + return fmt.Errorf("cannot parse rfc822Name %q", email) + } + if err := checkNameConstraints("Email address", email, mailbox, e.permittedEmailAddresses, e.excludedEmailAddresses, + func(parsedName, constraint any) (bool, error) { + return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string)) + }, + ); err != nil { + return err + } + } + + for _, uri := range uris { + if err := checkNameConstraints("URI", uri.String(), uri, e.permittedURIDomains, e.excludedURIDomains, + func(parsedName, constraint any) (bool, error) { + return matchURIConstraint(parsedName.(*url.URL), constraint.(string)) + }, + ); err != nil { + return err + } + } + + return nil +} + +// ValidateCertificate validates the DNS names, IP addresses, Email addresses +// and URIs present in the given certificate. +func (e *Engine) ValidateCertificate(cert *x509.Certificate) error { + return e.Validate(cert.DNSNames, cert.IPAddresses, cert.EmailAddresses, cert.URIs) +} diff --git a/authority/internal/constraints/constraints_test.go b/authority/internal/constraints/constraints_test.go new file mode 100644 index 00000000..0f6d0ef1 --- /dev/null +++ b/authority/internal/constraints/constraints_test.go @@ -0,0 +1,334 @@ +package constraints + +import ( + "crypto/x509" + "net" + "net/url" + "reflect" + "testing" + + "go.step.sm/crypto/minica" +) + +func TestNew(t *testing.T) { + ca1, err := minica.New() + if err != nil { + t.Fatal(err) + } + + ca2, err := minica.New( + minica.WithIntermediateTemplate(`{ + "subject": {{ toJson .Subject }}, + "keyUsage": ["certSign", "crlSign"], + "basicConstraints": { + "isCA": true, + "maxPathLen": 0 + }, + "nameConstraints": { + "critical": true, + "permittedDNSDomains": ["internal.example.org"], + "excludedDNSDomains": ["internal.example.com"], + "permittedIPRanges": ["192.168.1.0/24", "192.168.2.1/32"], + "excludedIPRanges": ["192.168.3.0/24", "192.168.4.0/28"], + "permittedEmailAddresses": ["root@example.org", "example.org", ".acme.org"], + "excludedEmailAddresses": ["root@example.com", "example.com", ".acme.com"], + "permittedURIDomains": ["host.example.org", ".acme.org"], + "excludedURIDomains": ["host.example.com", ".acme.com"] + } + }`), + ) + if err != nil { + t.Fatal(err) + } + + type args struct { + chain []*x509.Certificate + } + tests := []struct { + name string + args args + want *Engine + }{ + {"ok", args{[]*x509.Certificate{ca1.Intermediate, ca1.Root}}, &Engine{ + hasNameConstraints: false, + }}, + {"ok with constraints", args{[]*x509.Certificate{ca2.Intermediate, ca2.Root}}, &Engine{ + hasNameConstraints: true, + permittedDNSDomains: []string{"internal.example.org"}, + excludedDNSDomains: []string{"internal.example.com"}, + permittedIPRanges: []*net.IPNet{ + {IP: net.ParseIP("192.168.1.0").To4(), Mask: net.IPMask{255, 255, 255, 0}}, + {IP: net.ParseIP("192.168.2.1").To4(), Mask: net.IPMask{255, 255, 255, 255}}, + }, + excludedIPRanges: []*net.IPNet{ + {IP: net.ParseIP("192.168.3.0").To4(), Mask: net.IPMask{255, 255, 255, 0}}, + {IP: net.ParseIP("192.168.4.0").To4(), Mask: net.IPMask{255, 255, 255, 240}}, + }, + permittedEmailAddresses: []string{"root@example.org", "example.org", ".acme.org"}, + excludedEmailAddresses: []string{"root@example.com", "example.com", ".acme.com"}, + permittedURIDomains: []string{"host.example.org", ".acme.org"}, + excludedURIDomains: []string{"host.example.com", ".acme.com"}, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(tt.args.chain...); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNew_hasNameConstraints(t *testing.T) { + tests := []struct { + name string + fn func(c *x509.Certificate) + want bool + }{ + {"no constraints", func(c *x509.Certificate) {}, false}, + {"permittedDNSDomains", func(c *x509.Certificate) { c.PermittedDNSDomains = []string{"constraint"} }, true}, + {"excludedDNSDomains", func(c *x509.Certificate) { c.ExcludedDNSDomains = []string{"constraint"} }, true}, + {"permittedIPRanges", func(c *x509.Certificate) { + c.PermittedIPRanges = []*net.IPNet{{IP: net.ParseIP("192.168.3.0").To4(), Mask: net.IPMask{255, 255, 255, 0}}} + }, true}, + {"excludedIPRanges", func(c *x509.Certificate) { + c.ExcludedIPRanges = []*net.IPNet{{IP: net.ParseIP("192.168.3.0").To4(), Mask: net.IPMask{255, 255, 255, 0}}} + }, true}, + {"permittedEmailAddresses", func(c *x509.Certificate) { c.PermittedEmailAddresses = []string{"constraint"} }, true}, + {"excludedEmailAddresses", func(c *x509.Certificate) { c.ExcludedEmailAddresses = []string{"constraint"} }, true}, + {"permittedURIDomains", func(c *x509.Certificate) { c.PermittedURIDomains = []string{"constraint"} }, true}, + {"excludedURIDomains", func(c *x509.Certificate) { c.ExcludedURIDomains = []string{"constraint"} }, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cert := &x509.Certificate{} + tt.fn(cert) + if e := New(cert); e.hasNameConstraints != tt.want { + t.Errorf("Engine.hasNameConstraints = %v, want %v", e.hasNameConstraints, tt.want) + } + }) + } +} + +func TestEngine_Validate(t *testing.T) { + type fields struct { + hasNameConstraints bool + permittedDNSDomains []string + excludedDNSDomains []string + permittedIPRanges []*net.IPNet + excludedIPRanges []*net.IPNet + permittedEmailAddresses []string + excludedEmailAddresses []string + permittedURIDomains []string + excludedURIDomains []string + } + type args struct { + dnsNames []string + ipAddresses []net.IP + emailAddresses []string + uris []*url.URL + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + {"ok", fields{hasNameConstraints: false}, args{ + dnsNames: []string{"example.com", "host.example.com"}, + ipAddresses: []net.IP{{192, 168, 1, 1}, {0x26, 0x00, 0x1f, 0x1c, 0x47, 0x01, 0x9d, 0x00, 0xc3, 0xa7, 0x66, 0x94, 0x87, 0x0f, 0x20, 0x72}}, + emailAddresses: []string{"root@example.com"}, + uris: []*url.URL{{Scheme: "https", Host: "example.com", Path: "/uuid/c6d1a755-0c12-431e-9136-b64cb3173ec7"}}, + }, false}, + {"ok permitted dns", fields{ + hasNameConstraints: true, + permittedDNSDomains: []string{"example.com"}, + }, args{dnsNames: []string{"example.com", "www.example.com"}}, false}, + {"ok not excluded dns", fields{ + hasNameConstraints: true, + excludedDNSDomains: []string{"example.org"}, + }, args{dnsNames: []string{"example.com", "www.example.com"}}, false}, + {"ok permitted ip", fields{ + hasNameConstraints: true, + permittedIPRanges: []*net.IPNet{ + {IP: net.ParseIP("192.168.1.0"), Mask: net.IPMask{255, 255, 255, 0}}, + {IP: net.ParseIP("192.168.2.1").To4(), Mask: net.IPMask{255, 255, 255, 255}}, + {IP: net.ParseIP("2600:1700:22f8:2600:e559:bd88:350a:34d6"), Mask: net.IPMask{255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + }, + }, args{ipAddresses: []net.IP{{192, 168, 1, 10}, {192, 168, 2, 1}, {0x26, 0x0, 0x17, 0x00, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc}}}, false}, + {"ok not excluded ip", fields{ + hasNameConstraints: true, + excludedIPRanges: []*net.IPNet{ + {IP: net.ParseIP("192.168.1.0"), Mask: net.IPMask{255, 255, 255, 0}}, + {IP: net.ParseIP("192.168.2.1").To4(), Mask: net.IPMask{255, 255, 255, 255}}, + }, + }, args{ipAddresses: []net.IP{{192, 168, 2, 2}, {192, 168, 3, 1}}}, false}, + {"ok permitted emails", fields{ + hasNameConstraints: true, + permittedEmailAddresses: []string{"root@example.com", "acme.org", ".acme.com"}, + }, args{emailAddresses: []string{"root@example.com", "name@acme.org", "name@coyote.acme.com", `"(quoted)"@www.acme.com`}}, false}, + {"ok not excluded emails", fields{ + hasNameConstraints: true, + excludedEmailAddresses: []string{"root@example.com", "acme.org", ".acme.com"}, + }, args{emailAddresses: []string{"name@example.com", "root@acme.com", "root@other.com"}}, false}, + {"ok permitted uris", fields{ + hasNameConstraints: true, + permittedURIDomains: []string{"example.com", ".acme.com"}, + }, args{uris: []*url.URL{{Scheme: "https", Host: "example.com", Path: "/path"}, {Scheme: "https", Host: "www.acme.com", Path: "/path"}}}, false}, + {"ok not excluded uris", fields{ + hasNameConstraints: true, + excludedURIDomains: []string{"example.com", ".acme.com"}, + }, args{uris: []*url.URL{{Scheme: "https", Host: "example.org", Path: "/path"}, {Scheme: "https", Host: "acme.com", Path: "/path"}}}, false}, + {"fail permitted dns", fields{ + hasNameConstraints: true, + permittedDNSDomains: []string{"example.com"}, + }, args{dnsNames: []string{"www.example.com", "www.example.org"}}, true}, + {"fail not excluded dns", fields{ + hasNameConstraints: true, + excludedDNSDomains: []string{"example.org"}, + }, args{dnsNames: []string{"example.com", "www.example.org"}}, true}, + {"fail permitted ip", fields{ + hasNameConstraints: true, + permittedIPRanges: []*net.IPNet{ + {IP: net.ParseIP("192.168.1.0").To4(), Mask: net.IPMask{255, 255, 255, 0}}, + {IP: net.ParseIP("192.168.2.1").To4(), Mask: net.IPMask{255, 255, 255, 255}}, + }, + }, args{ipAddresses: []net.IP{{192, 168, 1, 10}, {192, 168, 2, 10}}}, true}, + {"fail not excluded ip", fields{ + hasNameConstraints: true, + excludedIPRanges: []*net.IPNet{ + {IP: net.ParseIP("192.168.1.0").To4(), Mask: net.IPMask{255, 255, 255, 0}}, + {IP: net.ParseIP("192.168.2.1").To4(), Mask: net.IPMask{255, 255, 255, 255}}, + }, + }, args{ipAddresses: []net.IP{{192, 168, 2, 2}, {192, 168, 1, 1}}}, true}, + {"fail permitted emails", fields{ + hasNameConstraints: true, + permittedEmailAddresses: []string{"root@example.com", "acme.org", ".acme.com"}, + }, args{emailAddresses: []string{"root@example.com", "name@acme.org", "name@acme.com"}}, true}, + {"fail not excluded emails", fields{ + hasNameConstraints: true, + excludedEmailAddresses: []string{"root@example.com", "acme.org", ".acme.com"}, + }, args{emailAddresses: []string{"name@example.com", "root@example.com"}}, true}, + {"fail permitted uris", fields{ + hasNameConstraints: true, + permittedURIDomains: []string{"example.com", ".acme.com"}, + }, args{uris: []*url.URL{{Scheme: "https", Host: "example.com", Path: "/path"}, {Scheme: "https", Host: "acme.com", Path: "/path"}}}, true}, + {"fail not excluded uris", fields{ + hasNameConstraints: true, + excludedURIDomains: []string{"example.com", ".acme.com"}, + }, args{uris: []*url.URL{{Scheme: "https", Host: "www.example.com", Path: "/path"}, {Scheme: "https", Host: "acme.com", Path: "/path"}}}, true}, + {"fail parse emails", fields{ + hasNameConstraints: true, + permittedEmailAddresses: []string{"example.com"}, + }, args{emailAddresses: []string{`(notquoted)@example.com`}}, true}, + {"fail match dns", fields{ + hasNameConstraints: true, + permittedDNSDomains: []string{"example.com"}, + }, args{dnsNames: []string{`www.example.com.`}}, true}, + {"fail match email", fields{ + hasNameConstraints: true, + excludedEmailAddresses: []string{`(notquoted)@example.com`}, + }, args{emailAddresses: []string{`ok@example.com`}}, true}, + {"fail match uri", fields{ + hasNameConstraints: true, + permittedURIDomains: []string{"example.com"}, + }, args{uris: []*url.URL{{Scheme: "urn", Opaque: "uuid:36efb1ae-6617-4b23-b799-874a37aaea1c"}}}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &Engine{ + hasNameConstraints: tt.fields.hasNameConstraints, + permittedDNSDomains: tt.fields.permittedDNSDomains, + excludedDNSDomains: tt.fields.excludedDNSDomains, + permittedIPRanges: tt.fields.permittedIPRanges, + excludedIPRanges: tt.fields.excludedIPRanges, + permittedEmailAddresses: tt.fields.permittedEmailAddresses, + excludedEmailAddresses: tt.fields.excludedEmailAddresses, + permittedURIDomains: tt.fields.permittedURIDomains, + excludedURIDomains: tt.fields.excludedURIDomains, + } + if err := e.Validate(tt.args.dnsNames, tt.args.ipAddresses, tt.args.emailAddresses, tt.args.uris); (err != nil) != tt.wantErr { + t.Errorf("service.Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestEngine_Validate_nil(t *testing.T) { + var e *Engine + if err := e.Validate([]string{"www.example.com"}, nil, nil, nil); err != nil { + t.Errorf("service.Validate() error = %v, wantErr false", err) + } +} + +func TestEngine_ValidateCertificate(t *testing.T) { + type fields struct { + hasNameConstraints bool + permittedDNSDomains []string + excludedDNSDomains []string + permittedIPRanges []*net.IPNet + excludedIPRanges []*net.IPNet + permittedEmailAddresses []string + excludedEmailAddresses []string + permittedURIDomains []string + excludedURIDomains []string + } + type args struct { + cert *x509.Certificate + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + {"ok", fields{hasNameConstraints: false}, args{&x509.Certificate{ + DNSNames: []string{"example.com"}, + IPAddresses: []net.IP{{127, 0, 0, 1}}, + EmailAddresses: []string{"info@example.com"}, + URIs: []*url.URL{{Scheme: "https", Host: "uuid.example.com", Path: "/dc4c76b5-5262-4551-a881-48094a604d63"}}, + }}, false}, + {"ok with constraints", fields{ + hasNameConstraints: true, + permittedDNSDomains: []string{"example.com"}, + permittedIPRanges: []*net.IPNet{ + {IP: net.ParseIP("127.0.0.1").To4(), Mask: net.IPMask{255, 255, 255, 255}}, + {IP: net.ParseIP("10.3.0.0").To4(), Mask: net.IPMask{255, 255, 0, 0}}, + }, + permittedEmailAddresses: []string{"example.com"}, + permittedURIDomains: []string{".example.com"}, + }, args{&x509.Certificate{ + DNSNames: []string{"www.example.com"}, + IPAddresses: []net.IP{{127, 0, 0, 1}, {10, 3, 1, 1}}, + EmailAddresses: []string{"info@example.com"}, + URIs: []*url.URL{{Scheme: "https", Host: "uuid.example.com", Path: "/dc4c76b5-5262-4551-a881-48094a604d63"}}, + }}, false}, + {"fail", fields{ + hasNameConstraints: true, + permittedURIDomains: []string{".example.com"}, + }, args{&x509.Certificate{ + DNSNames: []string{"example.com"}, + IPAddresses: []net.IP{{127, 0, 0, 1}}, + EmailAddresses: []string{"info@example.com"}, + URIs: []*url.URL{{Scheme: "https", Host: "uuid.example.org", Path: "/dc4c76b5-5262-4551-a881-48094a604d63"}}, + }}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := &Engine{ + hasNameConstraints: tt.fields.hasNameConstraints, + permittedDNSDomains: tt.fields.permittedDNSDomains, + excludedDNSDomains: tt.fields.excludedDNSDomains, + permittedIPRanges: tt.fields.permittedIPRanges, + excludedIPRanges: tt.fields.excludedIPRanges, + permittedEmailAddresses: tt.fields.permittedEmailAddresses, + excludedEmailAddresses: tt.fields.excludedEmailAddresses, + permittedURIDomains: tt.fields.permittedURIDomains, + excludedURIDomains: tt.fields.excludedURIDomains, + } + if err := e.ValidateCertificate(tt.args.cert); (err != nil) != tt.wantErr { + t.Errorf("Engine.ValidateCertificate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/authority/internal/constraints/verify.go b/authority/internal/constraints/verify.go new file mode 100644 index 00000000..5d070f1e --- /dev/null +++ b/authority/internal/constraints/verify.go @@ -0,0 +1,383 @@ +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package constraints + +import ( + "bytes" + "fmt" + "net" + "net/url" + "reflect" + "strings" +) + +func checkNameConstraints(nameType, name string, parsedName, permitted, excluded any, match func(name, constraint any) (bool, error)) error { + excludedValue := reflect.ValueOf(excluded) + for i := 0; i < excludedValue.Len(); i++ { + constraint := excludedValue.Index(i).Interface() + match, err := match(parsedName, constraint) + if err != nil { + return ConstraintError{ + Type: nameType, + Name: name, + Detail: err.Error(), + } + } + + if match { + return ConstraintError{ + Type: nameType, + Name: name, + Detail: fmt.Sprintf("%s %q is excluded by constraint %q", nameType, name, constraint), + } + } + } + + var ( + err error + ok = true + ) + + permittedValue := reflect.ValueOf(permitted) + for i := 0; i < permittedValue.Len(); i++ { + constraint := permittedValue.Index(i).Interface() + if ok, err = match(parsedName, constraint); err != nil { + return ConstraintError{ + Type: nameType, + Name: name, + Detail: err.Error(), + } + } + if ok { + break + } + } + if !ok { + return ConstraintError{ + Type: nameType, + Name: name, + Detail: fmt.Sprintf("%s %q is not permitted by any constraint", nameType, name), + } + } + + return nil +} + +func matchDomainConstraint(domain, constraint string) (bool, error) { + // The meaning of zero length constraints is not specified, but this + // code follows NSS and accepts them as matching everything. + if constraint == "" { + return true, nil + } + + domainLabels, ok := domainToReverseLabels(domain) + if !ok { + return false, fmt.Errorf("internal error: cannot parse domain %q", domain) + } + + // RFC 5280 says that a leading period in a domain name means that at least + // one label must be prepended, but only for URI and email constraints, not + // DNS constraints. The code also supports that behavior for DNS + // constraints. + + mustHaveSubdomains := false + if constraint[0] == '.' { + mustHaveSubdomains = true + constraint = constraint[1:] + } + + constraintLabels, ok := domainToReverseLabels(constraint) + if !ok { + return false, fmt.Errorf("internal error: cannot parse domain %q", constraint) + } + + if len(domainLabels) < len(constraintLabels) || + (mustHaveSubdomains && len(domainLabels) == len(constraintLabels)) { + return false, nil + } + + for i, constraintLabel := range constraintLabels { + if !strings.EqualFold(constraintLabel, domainLabels[i]) { + return false, nil + } + } + + return true, nil +} + +func normalizeIP(ip net.IP) net.IP { + if ip4 := ip.To4(); ip4 != nil { + return ip4 + } + return ip +} + +func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { + ip = normalizeIP(ip) + constraintIP := normalizeIP(constraint.IP) + if len(ip) != len(constraintIP) { + return false, nil + } + + for i := range ip { + if mask := constraint.Mask[i]; ip[i]&mask != constraintIP[i]&mask { + return false, nil + } + } + + return true, nil +} + +func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) { + // If the constraint contains an @, then it specifies an exact mailbox + // name. + if strings.Contains(constraint, "@") { + constraintMailbox, ok := parseRFC2821Mailbox(constraint) + if !ok { + return false, fmt.Errorf("internal error: cannot parse constraint %q", constraint) + } + return mailbox.local == constraintMailbox.local && strings.EqualFold(mailbox.domain, constraintMailbox.domain), nil + } + + // Otherwise the constraint is like a DNS constraint of the domain part + // of the mailbox. + return matchDomainConstraint(mailbox.domain, constraint) +} + +func matchURIConstraint(uri *url.URL, constraint string) (bool, error) { + // From RFC 5280, Section 4.2.1.10: + // “a uniformResourceIdentifier that does not include an authority + // component with a host name specified as a fully qualified domain + // name (e.g., if the URI either does not include an authority + // component or includes an authority component in which the host name + // is specified as an IP address), then the application MUST reject the + // certificate.” + + host := uri.Host + if host == "" { + return false, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", uri.String()) + } + + if strings.Contains(host, ":") && !strings.HasSuffix(host, "]") { + var err error + host, _, err = net.SplitHostPort(uri.Host) + if err != nil { + return false, err + } + } + + if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") || + net.ParseIP(host) != nil { + return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String()) + } + + return matchDomainConstraint(host, constraint) +} + +// domainToReverseLabels converts a textual domain name like foo.example.com to +// the list of labels in reverse order, e.g. ["com", "example", "foo"]. +func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { + for len(domain) > 0 { + if i := strings.LastIndexByte(domain, '.'); i == -1 { + reverseLabels = append(reverseLabels, domain) + domain = "" + } else { + reverseLabels = append(reverseLabels, domain[i+1:]) + domain = domain[:i] + } + } + + if len(reverseLabels) > 0 && reverseLabels[0] == "" { + // An empty label at the end indicates an absolute value. + return nil, false + } + + for _, label := range reverseLabels { + if label == "" { + // Empty labels are otherwise invalid. + return nil, false + } + + for _, c := range label { + if c < 33 || c > 126 { + // Invalid character. + return nil, false + } + } + } + + return reverseLabels, true +} + +// rfc2821Mailbox represents a “mailbox” (which is an email address to most +// people) by breaking it into the “local” (i.e. before the '@') and “domain” +// parts. +type rfc2821Mailbox struct { + local, domain string +} + +// parseRFC2821Mailbox parses an email address into local and domain parts, +// based on the ABNF for a “Mailbox” from RFC 2821. According to RFC 5280, +// Section 4.2.1.6 that's correct for an rfc822Name from a certificate: “The +// format of an rfc822Name is a "Mailbox" as defined in RFC 2821, Section 4.1.2”. +func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) { + if in == "" { + return mailbox, false + } + + localPartBytes := make([]byte, 0, len(in)/2) + + if in[0] == '"' { + // Quoted-string = DQUOTE *qcontent DQUOTE + // non-whitespace-control = %d1-8 / %d11 / %d12 / %d14-31 / %d127 + // qcontent = qtext / quoted-pair + // qtext = non-whitespace-control / + // %d33 / %d35-91 / %d93-126 + // quoted-pair = ("\" text) / obs-qp + // text = %d1-9 / %d11 / %d12 / %d14-127 / obs-text + // + // (Names beginning with “obs-” are the obsolete syntax from RFC 2822, + // Section 4. Since it has been 16 years, we no longer accept that.) + in = in[1:] + QuotedString: + for { + if in == "" { + return mailbox, false + } + c := in[0] + in = in[1:] + + switch { + case c == '"': + break QuotedString + + case c == '\\': + // quoted-pair + if in == "" { + return mailbox, false + } + if in[0] == 11 || + in[0] == 12 || + (1 <= in[0] && in[0] <= 9) || + (14 <= in[0] && in[0] <= 127) { + localPartBytes = append(localPartBytes, in[0]) + in = in[1:] + } else { + return mailbox, false + } + + case c == 11 || + c == 12 || + // Space (char 32) is not allowed based on the + // BNF, but RFC 3696 gives an example that + // assumes that it is. Several “verified” + // errata continue to argue about this point. + // We choose to accept it. + c == 32 || + c == 33 || + c == 127 || + (1 <= c && c <= 8) || + (14 <= c && c <= 31) || + (35 <= c && c <= 91) || + (93 <= c && c <= 126): + // qtext + localPartBytes = append(localPartBytes, c) + + default: + return mailbox, false + } + } + } else { + // Atom ("." Atom)* + NextChar: + for len(in) > 0 { + // atext from RFC 2822, Section 3.2.4 + c := in[0] + + switch { + case c == '\\': + // Examples given in RFC 3696 suggest that + // escaped characters can appear outside of a + // quoted string. Several “verified” errata + // continue to argue the point. We choose to + // accept it. + in = in[1:] + if in == "" { + return mailbox, false + } + fallthrough + + case ('0' <= c && c <= '9') || + ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + c == '!' || c == '#' || c == '$' || c == '%' || + c == '&' || c == '\'' || c == '*' || c == '+' || + c == '-' || c == '/' || c == '=' || c == '?' || + c == '^' || c == '_' || c == '`' || c == '{' || + c == '|' || c == '}' || c == '~' || c == '.': + localPartBytes = append(localPartBytes, in[0]) + in = in[1:] + + default: + break NextChar + } + } + + if len(localPartBytes) == 0 { + return mailbox, false + } + + // From RFC 3696, Section 3: + // “period (".") may also appear, but may not be used to start + // or end the local part, nor may two or more consecutive + // periods appear.” + twoDots := []byte{'.', '.'} + if localPartBytes[0] == '.' || + localPartBytes[len(localPartBytes)-1] == '.' || + bytes.Contains(localPartBytes, twoDots) { + return mailbox, false + } + } + + if in == "" || in[0] != '@' { + return mailbox, false + } + in = in[1:] + + // The RFC species a format for domains, but that's known to be + // violated in practice so we accept that anything after an '@' is the + // domain part. + if _, ok := domainToReverseLabels(in); !ok { + return mailbox, false + } + + mailbox.local = string(localPartBytes) + mailbox.domain = in + return mailbox, true +} diff --git a/authority/linkedca.go b/authority/linkedca.go index 133ae616..7a950c02 100644 --- a/authority/linkedca.go +++ b/authority/linkedca.go @@ -278,6 +278,7 @@ func (c *linkedCaClient) StoreCertificateChain(p provisioner.Interface, fullchai PemCertificate: serializeCertificateChain(fullchain[0]), PemCertificateChain: serializeCertificateChain(fullchain[1:]...), Provisioner: createProvisionerIdentity(p), + AttestationData: createAttestationData(p), RaProvisioner: raProvisioner, EndpointId: endpointID, }) @@ -395,26 +396,34 @@ func createProvisionerIdentity(p provisioner.Interface) *linkedca.ProvisionerIde } } -type raProvisioner interface { - RAInfo() *provisioner.RAInfo -} - func createRegistrationAuthorityProvisioner(p provisioner.Interface) (*linkedca.RegistrationAuthorityProvisioner, string) { if rap, ok := p.(raProvisioner); ok { - info := rap.RAInfo() - typ := linkedca.Provisioner_Type_value[strings.ToUpper(info.ProvisionerType)] - return &linkedca.RegistrationAuthorityProvisioner{ - AuthorityId: info.AuthorityID, - Provisioner: &linkedca.ProvisionerIdentity{ - Id: info.ProvisionerID, - Type: linkedca.Provisioner_Type(typ), - Name: info.ProvisionerName, - }, - }, info.EndpointID + if info := rap.RAInfo(); info != nil { + typ := linkedca.Provisioner_Type_value[strings.ToUpper(info.ProvisionerType)] + return &linkedca.RegistrationAuthorityProvisioner{ + AuthorityId: info.AuthorityID, + Provisioner: &linkedca.ProvisionerIdentity{ + Id: info.ProvisionerID, + Type: linkedca.Provisioner_Type(typ), + Name: info.ProvisionerName, + }, + }, info.EndpointID + } } return nil, "" } +func createAttestationData(p provisioner.Interface) *linkedca.AttestationData { + if ap, ok := p.(attProvisioner); ok { + if data := ap.AttestationData(); data != nil { + return &linkedca.AttestationData{ + PermanentIdentifier: data.PermanentIdentifier, + } + } + } + return nil +} + func serializeCertificate(crt *x509.Certificate) string { if crt == nil { return "" @@ -461,7 +470,7 @@ func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error) defer cancel() conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ - // nolint:gosec // used in bootstrap protocol + //nolint:gosec // used in bootstrap protocol InsecureSkipVerify: true, // lgtm[go/disabled-certificate-check] }))) if err != nil { diff --git a/authority/options.go b/authority/options.go index 9cef89f0..bf443ed6 100644 --- a/authority/options.go +++ b/authority/options.go @@ -5,6 +5,7 @@ import ( "crypto" "crypto/x509" "encoding/pem" + "net/http" "github.com/pkg/errors" "golang.org/x/crypto/ssh" @@ -85,6 +86,22 @@ func WithDatabase(d db.AuthDB) Option { } } +// WithQuietInit disables log output when the authority is initialized. +func WithQuietInit() Option { + return func(a *Authority) error { + a.quietInit = true + return nil + } +} + +// WithWebhookClient sets the http.Client to be used for outbound requests. +func WithWebhookClient(c *http.Client) Option { + return func(a *Authority) error { + a.webhookClient = c + return nil + } +} + // WithGetIdentityFunc sets a custom function to retrieve the identity from // an external resource. func WithGetIdentityFunc(fn func(ctx context.Context, p provisioner.Interface, email string) (*provisioner.Identity, error)) Option { @@ -151,16 +168,23 @@ func WithKeyManager(k kms.KeyManager) Option { // WithX509Signer defines the signer used to sign X509 certificates. func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option { + return WithX509SignerChain([]*x509.Certificate{crt}, s) +} + +// WithX509SignerChain defines the signer used to sign X509 certificates. This +// option is similar to WithX509Signer but it supports a chain of intermediates. +func WithX509SignerChain(issuerChain []*x509.Certificate, s crypto.Signer) Option { return func(a *Authority) error { srv, err := cas.New(context.Background(), casapi.Options{ Type: casapi.SoftCAS, Signer: s, - CertificateChain: []*x509.Certificate{crt}, + CertificateChain: issuerChain, }) if err != nil { return err } a.x509CAService = srv + a.intermediateX509Certs = append(a.intermediateX509Certs, issuerChain...) return nil } } @@ -233,6 +257,25 @@ func WithX509FederatedCerts(certs ...*x509.Certificate) Option { } } +// WithX509IntermediateCerts is an option that allows to define the list of +// intermediate certificates that the CA will be using. This option will replace +// any intermediate certificate defined before. +// +// Note that these certificates will not be bundled with the certificates signed +// by the CA, because the CAS service will take care of that. They should match, +// but that's not guaranteed. These certificates will be mainly used for name +// constraint validation before a certificate is issued. +// +// This option should only be used on specific configurations, for example when +// WithX509SignerFunc is used, as we don't know the list of intermediates in +// advance. +func WithX509IntermediateCerts(intermediateCerts ...*x509.Certificate) Option { + return func(a *Authority) error { + a.intermediateX509Certs = intermediateCerts + return nil + } +} + // WithX509RootBundle is an option that allows to define the list of root // certificates. This option will replace any root certificate defined before. func WithX509RootBundle(pemCerts []byte) Option { diff --git a/authority/policy.go b/authority/policy.go index 258873af..d3078e10 100644 --- a/authority/policy.go +++ b/authority/policy.go @@ -119,7 +119,6 @@ func (a *Authority) RemoveAuthorityPolicy(ctx context.Context) error { } func (a *Authority) checkAuthorityPolicy(ctx context.Context, currentAdmin *linkedca.Admin, p *linkedca.Policy) error { - // no policy and thus nothing to evaluate; return early if p == nil { return nil @@ -138,7 +137,6 @@ func (a *Authority) checkAuthorityPolicy(ctx context.Context, currentAdmin *link } func (a *Authority) checkProvisionerPolicy(ctx context.Context, provName string, p *linkedca.Policy) error { - // no policy and thus nothing to evaluate; return early if p == nil { return nil @@ -157,7 +155,6 @@ func (a *Authority) checkProvisionerPolicy(ctx context.Context, provName string, // checkPolicy checks if a new or updated policy configuration results in the user // locking themselves or other admins out of the CA. func (a *Authority) checkPolicy(ctx context.Context, currentAdmin *linkedca.Admin, otherAdmins []*linkedca.Admin, p *linkedca.Policy) error { - // convert the policy; return early if nil policyOptions := authPolicy.LinkedToCertificates(p) if policyOptions == nil { @@ -216,7 +213,6 @@ func (a *Authority) reloadPolicyEngines(ctx context.Context) error { ) if a.config.AuthorityConfig.EnableAdmin { - // temporarily disable policy loading when LinkedCA is in use if _, ok := a.adminDB.(*linkedCaClient); ok { return nil diff --git a/authority/policy/engine.go b/authority/policy/engine.go index 4b21f66b..d3881d9b 100644 --- a/authority/policy/engine.go +++ b/authority/policy/engine.go @@ -17,9 +17,9 @@ type Engine struct { // New returns a new Engine using Options. func New(options *Options) (*Engine, error) { - // if no options provided, return early if options == nil { + //nolint:nilnil // legacy return nil, nil } @@ -56,7 +56,6 @@ func New(options *Options) (*Engine, error) { // the X.509 policy (if available) and returns an error if one of the // names in the certificate is not allowed. func (e *Engine) IsX509CertificateAllowed(cert *x509.Certificate) error { - // return early if there's no policy to evaluate if e == nil || e.x509Policy == nil { return nil @@ -69,7 +68,6 @@ func (e *Engine) IsX509CertificateAllowed(cert *x509.Certificate) error { // AreSANsAllowed evaluates the slice of SANs against the X.509 policy // (if available) and returns an error if one of the SANs is not allowed. func (e *Engine) AreSANsAllowed(sans []string) error { - // return early if there's no policy to evaluate if e == nil || e.x509Policy == nil { return nil @@ -83,7 +81,6 @@ func (e *Engine) AreSANsAllowed(sans []string) error { // user or host policy (if configured) and returns an error if one of the // principals in the certificate is not allowed. func (e *Engine) IsSSHCertificateAllowed(cert *ssh.Certificate) error { - // return early if there's no policy to evaluate if e == nil || (e.sshHostPolicy == nil && e.sshUserPolicy == nil) { return nil diff --git a/authority/policy/policy.go b/authority/policy/policy.go index 3c53b704..96c7d7ea 100644 --- a/authority/policy/policy.go +++ b/authority/policy/policy.go @@ -19,7 +19,6 @@ type HostPolicy policy.SSHNamePolicyEngine // NewX509PolicyEngine creates a new x509 name policy engine func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy, error) { - // return early if no policy engine options to configure if policyOptions == nil { return nil, nil @@ -92,7 +91,6 @@ func NewSSHHostPolicyEngine(policyOptions SSHPolicyOptionsInterface) (HostPolicy // newSSHPolicyEngine creates a new SSH name policy engine func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEngineType) (policy.SSHNamePolicyEngine, error) { - // return early if no policy engine options to configure if policyOptions == nil { return nil, nil @@ -143,7 +141,6 @@ func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEn } func LinkedToCertificates(p *linkedca.Policy) *Options { - // return early if p == nil { return nil diff --git a/authority/policy_test.go b/authority/policy_test.go index 1dccf0d1..8e2e0df4 100644 --- a/authority/policy_test.go +++ b/authority/policy_test.go @@ -185,11 +185,11 @@ func TestAuthority_checkPolicy(t *testing.T) { } else { assert.IsType(t, &PolicyError{}, err) - pe, ok := err.(*PolicyError) - assert.True(t, ok) - - assert.Equal(t, tc.err.Typ, pe.Typ) - assert.Equal(t, tc.err.Error(), pe.Error()) + var pe *PolicyError + if assert.True(t, errors.As(err, &pe)) { + assert.Equal(t, tc.err.Typ, pe.Typ) + assert.Equal(t, tc.err.Error(), pe.Error()) + } } }) } @@ -1179,10 +1179,11 @@ func TestAuthority_RemoveAuthorityPolicy(t *testing.T) { } err := a.RemoveAuthorityPolicy(tt.args.ctx) if err != nil { - pe, ok := err.(*PolicyError) - assert.True(t, ok) - assert.Equal(t, tt.wantErr.Typ, pe.Typ) - assert.Equal(t, tt.wantErr.Err.Error(), pe.Err.Error()) + var pe *PolicyError + if assert.True(t, errors.As(err, &pe)) { + assert.Equal(t, tt.wantErr.Typ, pe.Typ) + assert.Equal(t, tt.wantErr.Err.Error(), pe.Err.Error()) + } return } }) @@ -1250,10 +1251,11 @@ func TestAuthority_GetAuthorityPolicy(t *testing.T) { } got, err := a.GetAuthorityPolicy(tt.args.ctx) if err != nil { - pe, ok := err.(*PolicyError) - assert.True(t, ok) - assert.Equal(t, tt.wantErr.Typ, pe.Typ) - assert.Equal(t, tt.wantErr.Err.Error(), pe.Err.Error()) + var pe *PolicyError + if assert.True(t, errors.As(err, &pe)) { + assert.Equal(t, tt.wantErr.Typ, pe.Typ) + assert.Equal(t, tt.wantErr.Err.Error(), pe.Err.Error()) + } return } if !reflect.DeepEqual(got, tt.want) { @@ -1429,10 +1431,11 @@ func TestAuthority_CreateAuthorityPolicy(t *testing.T) { } got, err := a.CreateAuthorityPolicy(tt.args.ctx, tt.args.adm, tt.args.p) if err != nil { - pe, ok := err.(*PolicyError) - assert.True(t, ok) - assert.Equal(t, tt.wantErr.Typ, pe.Typ) - assert.Equal(t, tt.wantErr.Err.Error(), pe.Err.Error()) + var pe *PolicyError + if assert.True(t, errors.As(err, &pe)) { + assert.Equal(t, tt.wantErr.Typ, pe.Typ) + assert.Equal(t, tt.wantErr.Err.Error(), pe.Err.Error()) + } return } if !reflect.DeepEqual(got, tt.want) { @@ -1611,10 +1614,11 @@ func TestAuthority_UpdateAuthorityPolicy(t *testing.T) { } got, err := a.UpdateAuthorityPolicy(tt.args.ctx, tt.args.adm, tt.args.p) if err != nil { - pe, ok := err.(*PolicyError) - assert.True(t, ok) - assert.Equal(t, tt.wantErr.Typ, pe.Typ) - assert.Equal(t, tt.wantErr.Err.Error(), pe.Err.Error()) + var pe *PolicyError + if assert.True(t, errors.As(err, &pe)) { + assert.Equal(t, tt.wantErr.Typ, pe.Typ) + assert.Equal(t, tt.wantErr.Err.Error(), pe.Err.Error()) + } return } if !reflect.DeepEqual(got, tt.want) { diff --git a/authority/provisioner/acme.go b/authority/provisioner/acme.go index 5955ac6a..688a3532 100644 --- a/authority/provisioner/acme.go +++ b/authority/provisioner/acme.go @@ -10,12 +10,13 @@ import ( "time" "github.com/pkg/errors" + "go.step.sm/linkedca" ) // ACMEChallenge represents the supported acme challenges. type ACMEChallenge string -// nolint:revive // better names +//nolint:stylecheck,revive // better names const ( // HTTP_01 is the http-01 ACME challenge. HTTP_01 ACMEChallenge = "http-01" @@ -83,6 +84,17 @@ type ACME struct { Type string `json:"type"` Name string `json:"name"` ForceCN bool `json:"forceCN,omitempty"` + // TermsOfService contains a URL pointing to the ACME server's + // terms of service. Defaults to empty. + TermsOfService string `json:"termsOfService,omitempty"` + // Website contains an URL pointing to more information about + // the ACME server. Defaults to empty. + Website string `json:"website,omitempty"` + // CaaIdentities is an array of hostnames that the ACME server + // identifies itself with. These hostnames can be used by ACME + // clients to determine the correct issuer domain name to use + // when configuring CAA records. Defaults to empty array. + CaaIdentities []string `json:"caaIdentities,omitempty"` // RequireEAB makes the provisioner require ACME EAB to be provided // by clients when creating a new Account. If set to true, the provided // EAB will be verified. If set to false and an EAB is provided, it is @@ -217,7 +229,6 @@ type ACMEIdentifier struct { // AuthorizeOrderIdentifier verifies the provisioner is allowed to issue a // certificate for an ACME Order Identifier. func (p *ACME) AuthorizeOrderIdentifier(ctx context.Context, identifier ACMEIdentifier) error { - x509Policy := p.ctl.getPolicy().getX509() // identifier is allowed if no policy is configured @@ -253,22 +264,22 @@ func (p *ACME) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e defaultPublicKeyValidator{}, newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), + p.ctl.newWebhookController(nil, linkedca.Webhook_X509), } return opts, nil } // AuthorizeRevoke is called just before the certificate is to be revoked by -// the CA. It can be used to authorize revocation of a certificate. It -// currently is a no-op. -// TODO(hs): add configuration option that toggles revocation? Or change function signature to make it more useful? -// Or move certain logic out of the Revoke API to here? Would likely involve some more stuff in the ctx. +// the CA. It can be used to authorize revocation of a certificate. With the +// ACME protocol, revocation authorization is specified and performed as part +// of the client/server interaction, so this is a no-op. func (p *ACME) AuthorizeRevoke(ctx context.Context, token string) error { return nil } // AuthorizeRenew returns an error if the renewal is disabled. -// NOTE: This method does not actually validate the certificate or check it's +// NOTE: This method does not actually validate the certificate or check its // revocation status. Just confirms that the provisioner that created the // certificate was configured to allow renewals. func (p *ACME) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { diff --git a/authority/provisioner/acme_118_test.go b/authority/provisioner/acme_118_test.go new file mode 100644 index 00000000..e47dd3f6 --- /dev/null +++ b/authority/provisioner/acme_118_test.go @@ -0,0 +1,82 @@ +//go:build go1.18 +// +build go1.18 + +package provisioner + +import ( + "bytes" + "crypto/x509" + "os" + "testing" +) + +func TestACME_GetAttestationRoots(t *testing.T) { + appleCA, err := os.ReadFile("testdata/certs/apple-att-ca.crt") + if err != nil { + t.Fatal(err) + } + yubicoCA, err := os.ReadFile("testdata/certs/yubico-piv-ca.crt") + if err != nil { + t.Fatal(err) + } + + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(appleCA) + pool.AppendCertsFromPEM(yubicoCA) + + type fields struct { + Type string + Name string + AttestationRoots []byte + } + tests := []struct { + name string + fields fields + want *x509.CertPool + want1 bool + }{ + {"ok", fields{"ACME", "acme", bytes.Join([][]byte{appleCA, yubicoCA}, []byte("\n"))}, pool, true}, + {"nil", fields{"ACME", "acme", nil}, nil, false}, + {"empty", fields{"ACME", "acme", []byte{}}, nil, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &ACME{ + Type: tt.fields.Type, + Name: tt.fields.Name, + AttestationRoots: tt.fields.AttestationRoots, + } + if err := p.Init(Config{ + Claims: globalProvisionerClaims, + Audiences: testAudiences, + }); err != nil { + t.Fatal(err) + } + got, got1 := p.GetAttestationRoots() + switch { + case tt.want == nil && got == nil: + break + case tt.want == nil && got != nil, tt.want != nil && got == nil: + t.Errorf("ACME.GetAttestationRoots() got = %v, want %v", got, tt.want) + default: + //nolint:staticcheck // this file only runs in go1.18 + gotSubjects := got.Subjects() + //nolint:staticcheck // this file only runs in go1.18 + wantSubjects := tt.want.Subjects() + if len(gotSubjects) != len(wantSubjects) { + t.Errorf("ACME.GetAttestationRoots() got = %v, want %v", got, tt.want) + } else { + for i, gotSub := range gotSubjects { + if !bytes.Equal(gotSub, wantSubjects[i]) { + t.Errorf("ACME.GetAttestationRoots() got = %v, want %v", got, tt.want) + break + } + } + } + } + if got1 != tt.want1 { + t.Errorf("ACME.GetAttestationRoots() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/authority/provisioner/acme_119_test.go b/authority/provisioner/acme_119_test.go new file mode 100644 index 00000000..608bdd82 --- /dev/null +++ b/authority/provisioner/acme_119_test.go @@ -0,0 +1,66 @@ +//go:build !go1.18 +// +build !go1.18 + +package provisioner + +import ( + "bytes" + "crypto/x509" + "os" + "testing" +) + +func TestACME_GetAttestationRoots(t *testing.T) { + appleCA, err := os.ReadFile("testdata/certs/apple-att-ca.crt") + if err != nil { + t.Fatal(err) + } + yubicoCA, err := os.ReadFile("testdata/certs/yubico-piv-ca.crt") + if err != nil { + t.Fatal(err) + } + + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(appleCA) + pool.AppendCertsFromPEM(yubicoCA) + + type fields struct { + Type string + Name string + AttestationRoots []byte + } + tests := []struct { + name string + fields fields + want *x509.CertPool + want1 bool + }{ + {"ok", fields{"ACME", "acme", bytes.Join([][]byte{appleCA, yubicoCA}, []byte("\n"))}, pool, true}, + {"nil", fields{"ACME", "acme", nil}, nil, false}, + {"empty", fields{"ACME", "acme", []byte{}}, nil, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &ACME{ + Type: tt.fields.Type, + Name: tt.fields.Name, + AttestationRoots: tt.fields.AttestationRoots, + } + if err := p.Init(Config{ + Claims: globalProvisionerClaims, + Audiences: testAudiences, + }); err != nil { + t.Fatal(err) + } + got, got1 := p.GetAttestationRoots() + if tt.want == nil && got != nil { + t.Errorf("ACME.GetAttestationRoots() got = %v, want %v", got, tt.want) + } else if !tt.want.Equal(got) { + t.Errorf("ACME.GetAttestationRoots() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("ACME.GetAttestationRoots() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/authority/provisioner/acme_test.go b/authority/provisioner/acme_test.go index bfd85303..94684ce1 100644 --- a/authority/provisioner/acme_test.go +++ b/authority/provisioner/acme_test.go @@ -1,3 +1,6 @@ +//go:build !go1.18 +// +build !go1.18 + package provisioner import ( @@ -266,7 +269,7 @@ func TestACME_AuthorizeSign(t *testing.T) { } } else { if assert.Nil(t, tc.err) && assert.NotNil(t, opts) { - assert.Equals(t, 7, len(opts)) // number of SignOptions returned + assert.Equals(t, 8, len(opts)) // number of SignOptions returned for _, o := range opts { switch v := o.(type) { case *ACME: @@ -285,6 +288,8 @@ func TestACME_AuthorizeSign(t *testing.T) { assert.Equals(t, v.max, tc.p.ctl.Claimer.MaxTLSCertDuration()) case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) + case *WebhookController: + assert.Len(t, 0, v.webhooks) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } @@ -371,58 +376,3 @@ func TestACME_IsAttestationFormatEnabled(t *testing.T) { }) } } - -func TestACME_GetAttestationRoots(t *testing.T) { - appleCA, err := os.ReadFile("testdata/certs/apple-att-ca.crt") - if err != nil { - t.Fatal(err) - } - yubicoCA, err := os.ReadFile("testdata/certs/yubico-piv-ca.crt") - if err != nil { - t.Fatal(err) - } - - pool := x509.NewCertPool() - pool.AppendCertsFromPEM(appleCA) - pool.AppendCertsFromPEM(yubicoCA) - - type fields struct { - Type string - Name string - AttestationRoots []byte - } - tests := []struct { - name string - fields fields - want *x509.CertPool - want1 bool - }{ - {"ok", fields{"ACME", "acme", bytes.Join([][]byte{appleCA, yubicoCA}, []byte("\n"))}, pool, true}, - {"nil", fields{"ACME", "acme", nil}, nil, false}, - {"empty", fields{"ACME", "acme", []byte{}}, nil, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - p := &ACME{ - Type: tt.fields.Type, - Name: tt.fields.Name, - AttestationRoots: tt.fields.AttestationRoots, - } - if err := p.Init(Config{ - Claims: globalProvisionerClaims, - Audiences: testAudiences, - }); err != nil { - t.Fatal(err) - } - got, got1 := p.GetAttestationRoots() - if tt.want == nil && got != nil { - t.Errorf("ACME.GetAttestationRoots() got = %v, want %v", got, tt.want) - } else if !tt.want.Equal(got) { - t.Errorf("ACME.GetAttestationRoots() got = %v, want %v", got, tt.want) - } - if got1 != tt.want1 { - t.Errorf("ACME.GetAttestationRoots() got1 = %v, want %v", got1, tt.want1) - } - }) - } -} diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 463a4aee..0560877c 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -21,6 +21,7 @@ import ( "go.step.sm/crypto/jose" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" "github.com/smallstep/certificates/errs" ) @@ -35,20 +36,17 @@ const awsIdentityURL = "http://169.254.169.254/latest/dynamic/instance-identity/ const awsSignatureURL = "http://169.254.169.254/latest/dynamic/instance-identity/signature" // awsAPITokenURL is the url used to get the IMDSv2 API token -// nolint:gosec // no credentials here -const awsAPITokenURL = "http://169.254.169.254/latest/api/token" +const awsAPITokenURL = "http://169.254.169.254/latest/api/token" //nolint:gosec // no credentials here // awsAPITokenTTL is the default TTL to use when requesting IMDSv2 API tokens // -- we keep this short-lived since we get a new token with every call to readURL() const awsAPITokenTTL = "30" // awsMetadataTokenHeader is the header that must be passed with every IMDSv2 request -// nolint:gosec // no credentials here -const awsMetadataTokenHeader = "X-aws-ec2-metadata-token" +const awsMetadataTokenHeader = "X-aws-ec2-metadata-token" //nolint:gosec // no credentials here // awsMetadataTokenTTLHeader is the header used to indicate the token TTL requested -// nolint:gosec // no credentials here -const awsMetadataTokenTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds" +const awsMetadataTokenTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds" //nolint:gosec // no credentials here // awsCertificate is the certificate used to validate the instance identity // signature. @@ -487,6 +485,7 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er commonNameValidator(payload.Claims.Subject), newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), + p.ctl.newWebhookController(data, linkedca.Webhook_X509), ), nil } @@ -768,5 +767,7 @@ func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, &sshCertDefaultValidator{}, // Ensure that all principal names are allowed newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil), + // Call webhooks + p.ctl.newWebhookController(data, linkedca.Webhook_SSH), ), nil } diff --git a/authority/provisioner/aws_test.go b/authority/provisioner/aws_test.go index 0660c3f0..668bc13b 100644 --- a/authority/provisioner/aws_test.go +++ b/authority/provisioner/aws_test.go @@ -522,8 +522,8 @@ func TestAWS_authorizeToken(t *testing.T) { tc := tt(t) if claims, err := tc.p.authorizeToken(tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -642,11 +642,11 @@ func TestAWS_AuthorizeSign(t *testing.T) { code int wantErr bool }{ - {"ok", p1, args{t1, "foo.local"}, 8, http.StatusOK, false}, - {"ok", p2, args{t2, "instance-id"}, 12, http.StatusOK, false}, - {"ok", p2, args{t2Hostname, "ip-127-0-0-1.us-west-1.compute.internal"}, 12, http.StatusOK, false}, - {"ok", p2, args{t2PrivateIP, "127.0.0.1"}, 12, http.StatusOK, false}, - {"ok", p1, args{t4, "instance-id"}, 8, http.StatusOK, false}, + {"ok", p1, args{t1, "foo.local"}, 9, http.StatusOK, false}, + {"ok", p2, args{t2, "instance-id"}, 13, http.StatusOK, false}, + {"ok", p2, args{t2Hostname, "ip-127-0-0-1.us-west-1.compute.internal"}, 13, http.StatusOK, false}, + {"ok", p2, args{t2PrivateIP, "127.0.0.1"}, 13, http.StatusOK, false}, + {"ok", p1, args{t4, "instance-id"}, 9, http.StatusOK, false}, {"fail account", p3, args{token: t3}, 0, http.StatusUnauthorized, true}, {"fail token", p1, args{token: "token"}, 0, http.StatusUnauthorized, true}, {"fail subject", p1, args{token: failSubject}, 0, http.StatusUnauthorized, true}, @@ -669,8 +669,8 @@ func TestAWS_AuthorizeSign(t *testing.T) { t.Errorf("AWS.AuthorizeSign() error = %v, wantErr %v", err, tt.wantErr) return case err != nil: - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) default: assert.Equals(t, tt.wantLen, len(got)) @@ -701,6 +701,8 @@ func TestAWS_AuthorizeSign(t *testing.T) { assert.Equals(t, []string(v), []string{"ip-127-0-0-1.us-west-1.compute.internal"}) case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) + case *WebhookController: + assert.Len(t, 0, v.webhooks) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } @@ -748,7 +750,7 @@ func TestAWS_AuthorizeSSHSign(t *testing.T) { pub := key.Public().Key rsa2048, err := rsa.GenerateKey(rand.Reader, 2048) assert.FatalError(t, err) - // nolint:gosec // tests minimum size of the key + //nolint:gosec // tests minimum size of the key rsa1024, err := rsa.GenerateKey(rand.Reader, 1024) assert.FatalError(t, err) @@ -807,8 +809,8 @@ func TestAWS_AuthorizeSSHSign(t *testing.T) { return } if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.Nil(t, got) } else if assert.NotNil(t, got) { @@ -864,8 +866,8 @@ func TestAWS_AuthorizeRenew(t *testing.T) { if err := tt.aws.AuthorizeRenew(context.Background(), tt.args.cert); (err != nil) != tt.wantErr { t.Errorf("AWS.AuthorizeRenew() error = %v, wantErr %v", err, tt.wantErr) } else if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) } }) diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index 3f714a3e..4b161d9c 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -17,6 +17,7 @@ import ( "go.step.sm/crypto/jose" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" "github.com/smallstep/certificates/errs" ) @@ -24,8 +25,7 @@ import ( // azureOIDCBaseURL is the base discovery url for Microsoft Azure tokens. const azureOIDCBaseURL = "https://login.microsoftonline.com" -// azureIdentityTokenURL is the URL to get the identity token for an instance. -// nolint:gosec // no credentials here +//nolint:gosec // azureIdentityTokenURL is the URL to get the identity token for an instance. const azureIdentityTokenURL = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F" // azureDefaultAudience is the default audience used. @@ -364,6 +364,7 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, defaultPublicKeyValidator{}, newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), + p.ctl.newWebhookController(data, linkedca.Webhook_X509), ), nil } @@ -432,6 +433,8 @@ func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio &sshCertDefaultValidator{}, // Ensure that all principal names are allowed newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil), + // Call webhooks + p.ctl.newWebhookController(data, linkedca.Webhook_SSH), ), nil } diff --git a/authority/provisioner/azure_test.go b/authority/provisioner/azure_test.go index 7f8b70d0..84f2ebbf 100644 --- a/authority/provisioner/azure_test.go +++ b/authority/provisioner/azure_test.go @@ -336,8 +336,8 @@ func TestAzure_authorizeToken(t *testing.T) { tc := tt(t) if claims, name, group, subscriptionID, objectID, err := tc.p.authorizeToken(tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -474,11 +474,11 @@ func TestAzure_AuthorizeSign(t *testing.T) { code int wantErr bool }{ - {"ok", p1, args{t1}, 7, http.StatusOK, false}, - {"ok", p2, args{t2}, 12, http.StatusOK, false}, - {"ok", p1, args{t11}, 7, http.StatusOK, false}, - {"ok", p5, args{t5}, 7, http.StatusOK, false}, - {"ok", p7, args{t7}, 7, http.StatusOK, false}, + {"ok", p1, args{t1}, 8, http.StatusOK, false}, + {"ok", p2, args{t2}, 13, http.StatusOK, false}, + {"ok", p1, args{t11}, 8, http.StatusOK, false}, + {"ok", p5, args{t5}, 8, http.StatusOK, false}, + {"ok", p7, args{t7}, 8, http.StatusOK, false}, {"fail tenant", p3, args{t3}, 0, http.StatusUnauthorized, true}, {"fail resource group", p4, args{t4}, 0, http.StatusUnauthorized, true}, {"fail subscription", p6, args{t6}, 0, http.StatusUnauthorized, true}, @@ -498,8 +498,8 @@ func TestAzure_AuthorizeSign(t *testing.T) { t.Errorf("Azure.AuthorizeSign() error = %v, wantErr %v", err, tt.wantErr) return case err != nil: - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) default: assert.Equals(t, tt.wantLen, len(got)) @@ -530,6 +530,8 @@ func TestAzure_AuthorizeSign(t *testing.T) { assert.Equals(t, []string(v), []string{"virtualMachine"}) case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) + case *WebhookController: + assert.Len(t, 0, v.webhooks) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } @@ -576,8 +578,8 @@ func TestAzure_AuthorizeRenew(t *testing.T) { if err := tt.azure.AuthorizeRenew(context.Background(), tt.args.cert); (err != nil) != tt.wantErr { t.Errorf("Azure.AuthorizeRenew() error = %v, wantErr %v", err, tt.wantErr) } else if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) } }) @@ -624,7 +626,7 @@ func TestAzure_AuthorizeSSHSign(t *testing.T) { pub := key.Public().Key rsa2048, err := rsa.GenerateKey(rand.Reader, 2048) assert.FatalError(t, err) - // nolint:gosec // tests minimum size of the key + //nolint:gosec // tests minimum size of the key rsa1024, err := rsa.GenerateKey(rand.Reader, 1024) assert.FatalError(t, err) @@ -673,8 +675,8 @@ func TestAzure_AuthorizeSSHSign(t *testing.T) { return } if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.Nil(t, got) } else if assert.NotNil(t, got) { diff --git a/authority/provisioner/claims.go b/authority/provisioner/claims.go index 96f19b37..b6a5a81e 100644 --- a/authority/provisioner/claims.go +++ b/authority/provisioner/claims.go @@ -38,7 +38,8 @@ type Claimer struct { // NewClaimer initializes a new claimer with the given claims. func NewClaimer(claims *Claims, global Claims) (*Claimer, error) { c := &Claimer{global: global, claims: claims} - return c, c.Validate() + err := c.Validate() + return c, err } // Claims returns the merge of the inner and global claims. diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index 85b489c1..c483a50d 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -1,7 +1,7 @@ package provisioner import ( - "crypto/sha1" // nolint:gosec // not used for cryptographic security + "crypto/sha1" //nolint:gosec // not used for cryptographic security "crypto/x509" "encoding/asn1" "encoding/binary" @@ -319,7 +319,7 @@ func loadProvisioner(m *sync.Map, key string) (Interface, bool) { // provisionerSum returns the SHA1 of the provisioners ID. From this we will // create the unique and sorted id. func provisionerSum(p Interface) []byte { - // nolint:gosec // not used for cryptographic security + //nolint:gosec // not used for cryptographic security sum := sha1.Sum([]byte(p.GetID())) return sum[:] } diff --git a/authority/provisioner/controller.go b/authority/provisioner/controller.go index 063ab50c..ef96639f 100644 --- a/authority/provisioner/controller.go +++ b/authority/provisioner/controller.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/errs" + "go.step.sm/linkedca" "golang.org/x/crypto/ssh" ) @@ -23,6 +24,8 @@ type Controller struct { AuthorizeRenewFunc AuthorizeRenewFunc AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc policy *policyEngine + webhookClient *http.Client + webhooks []*Webhook } // NewController initializes a new provisioner controller. @@ -43,6 +46,8 @@ func NewController(p Interface, claims *Claims, config Config, options *Options) AuthorizeRenewFunc: config.AuthorizeRenewFunc, AuthorizeSSHRenewFunc: config.AuthorizeSSHRenewFunc, policy: policy, + webhookClient: config.WebhookClient, + webhooks: options.GetWebhooks(), }, nil } @@ -72,6 +77,19 @@ func (c *Controller) AuthorizeSSHRenew(ctx context.Context, cert *ssh.Certificat return DefaultAuthorizeSSHRenew(ctx, c, cert) } +func (c *Controller) newWebhookController(templateData WebhookSetter, certType linkedca.Webhook_CertType) *WebhookController { + client := c.webhookClient + if client == nil { + client = http.DefaultClient + } + return &WebhookController{ + TemplateData: templateData, + client: client, + webhooks: c.webhooks, + certType: certType, + } +} + // Identity is the type representing an externally supplied identity that is used // by provisioners to populate certificate fields. type Identity struct { diff --git a/authority/provisioner/controller_test.go b/authority/provisioner/controller_test.go index 37cbfd89..c628f074 100644 --- a/authority/provisioner/controller_test.go +++ b/authority/provisioner/controller_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" "golang.org/x/crypto/ssh" "github.com/smallstep/certificates/authority/policy" @@ -445,3 +447,18 @@ func TestDefaultAuthorizeSSHRenew(t *testing.T) { }) } } + +func Test_newWebhookController(t *testing.T) { + c := &Controller{} + data := x509util.TemplateData{"foo": "bar"} + ctl := c.newWebhookController(data, linkedca.Webhook_X509) + if !reflect.DeepEqual(ctl.TemplateData, data) { + t.Error("Failed to set templateData") + } + if ctl.certType != linkedca.Webhook_X509 { + t.Error("Failed to set certType") + } + if ctl.client == nil { + t.Error("Failed to set client") + } +} diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index a116312d..e9b372b2 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -18,6 +18,7 @@ import ( "go.step.sm/crypto/jose" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" "github.com/smallstep/certificates/errs" ) @@ -102,7 +103,6 @@ func (p *GCP) GetID() string { return p.ID } return p.GetIDForToken() - } // GetIDForToken returns an identifier that will be used to load the provisioner @@ -273,6 +273,7 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er defaultPublicKeyValidator{}, newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), + p.ctl.newWebhookController(data, linkedca.Webhook_X509), ), nil } @@ -438,5 +439,7 @@ func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, &sshCertDefaultValidator{}, // Ensure that all principal names are allowed newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil), + // Call webhooks + p.ctl.newWebhookController(data, linkedca.Webhook_SSH), ), nil } diff --git a/authority/provisioner/gcp_test.go b/authority/provisioner/gcp_test.go index 3d6b5d75..7705b44a 100644 --- a/authority/provisioner/gcp_test.go +++ b/authority/provisioner/gcp_test.go @@ -391,8 +391,8 @@ func TestGCP_authorizeToken(t *testing.T) { tc := tt(t) if claims, err := tc.p.authorizeToken(tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -516,9 +516,9 @@ func TestGCP_AuthorizeSign(t *testing.T) { code int wantErr bool }{ - {"ok", p1, args{t1}, 7, http.StatusOK, false}, - {"ok", p2, args{t2}, 12, http.StatusOK, false}, - {"ok", p3, args{t3}, 7, http.StatusOK, false}, + {"ok", p1, args{t1}, 8, http.StatusOK, false}, + {"ok", p2, args{t2}, 13, http.StatusOK, false}, + {"ok", p3, args{t3}, 8, http.StatusOK, false}, {"fail token", p1, args{"token"}, 0, http.StatusUnauthorized, true}, {"fail key", p1, args{failKey}, 0, http.StatusUnauthorized, true}, {"fail iss", p1, args{failIss}, 0, http.StatusUnauthorized, true}, @@ -541,8 +541,8 @@ func TestGCP_AuthorizeSign(t *testing.T) { t.Errorf("GCP.AuthorizeSign() error = %v, wantErr %v", err, tt.wantErr) return case err != nil: - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) default: assert.Equals(t, tt.wantLen, len(got)) @@ -573,6 +573,8 @@ func TestGCP_AuthorizeSign(t *testing.T) { assert.Equals(t, []string(v), []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}) case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) + case *WebhookController: + assert.Len(t, 0, v.webhooks) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } @@ -623,7 +625,7 @@ func TestGCP_AuthorizeSSHSign(t *testing.T) { pub := key.Public().Key rsa2048, err := rsa.GenerateKey(rand.Reader, 2048) assert.FatalError(t, err) - // nolint:gosec // tests minimum size of the key + //nolint:gosec // tests minimum size of the key rsa1024, err := rsa.GenerateKey(rand.Reader, 1024) assert.FatalError(t, err) @@ -682,8 +684,8 @@ func TestGCP_AuthorizeSSHSign(t *testing.T) { return } if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.Nil(t, got) } else if assert.NotNil(t, got) { @@ -739,8 +741,8 @@ func TestGCP_AuthorizeRenew(t *testing.T) { if err := tt.prov.AuthorizeRenew(context.Background(), tt.args.cert); (err != nil) != tt.wantErr { t.Errorf("GCP.AuthorizeRenew() error = %v, wantErr %v", err, tt.wantErr) } else if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCoder interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) } }) diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index 5cfb0409..59332996 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -11,6 +11,7 @@ import ( "go.step.sm/crypto/jose" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" "github.com/smallstep/certificates/errs" ) @@ -194,6 +195,7 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er defaultSANsValidator(claims.SANs), newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), + p.ctl.newWebhookController(data, linkedca.Webhook_X509), }, nil } @@ -278,6 +280,8 @@ func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, &sshCertDefaultValidator{}, // Ensure that all principal names are allowed newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), p.ctl.getPolicy().getSSHUser()), + // Call webhooks + p.ctl.newWebhookController(data, linkedca.Webhook_SSH), ), nil } diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 723ccf56..19cee4fb 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -185,8 +185,8 @@ func TestJWK_authorizeToken(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if got, err := tt.prov.authorizeToken(tt.args.token, testAudiences.Sign); err != nil { if assert.NotNil(t, tt.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.HasPrefix(t, err.Error(), tt.err.Error()) } @@ -225,8 +225,8 @@ func TestJWK_AuthorizeRevoke(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if err := tt.prov.AuthorizeRevoke(context.Background(), tt.args.token); err != nil { if assert.NotNil(t, tt.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.HasPrefix(t, err.Error(), tt.err.Error()) } @@ -290,14 +290,14 @@ func TestJWK_AuthorizeSign(t *testing.T) { ctx := NewContextWithMethod(context.Background(), SignMethod) if got, err := tt.prov.AuthorizeSign(ctx, tt.args.token); err != nil { if assert.NotNil(t, tt.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.HasPrefix(t, err.Error(), tt.err.Error()) } } else { if assert.NotNil(t, got) { - assert.Equals(t, 9, len(got)) + assert.Equals(t, 10, len(got)) for _, o := range got { switch v := o.(type) { case *JWK: @@ -319,6 +319,7 @@ func TestJWK_AuthorizeSign(t *testing.T) { assert.Equals(t, []string(v), tt.sans) case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) + case *WebhookController: default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } @@ -366,8 +367,8 @@ func TestJWK_AuthorizeRenew(t *testing.T) { if err := tt.prov.AuthorizeRenew(context.Background(), tt.args.cert); (err != nil) != tt.wantErr { t.Errorf("JWK.AuthorizeRenew() error = %v, wantErr %v", err, tt.wantErr) } else if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) } }) @@ -411,7 +412,7 @@ func TestJWK_AuthorizeSSHSign(t *testing.T) { pub := key.Public().Key rsa2048, err := rsa.GenerateKey(rand.Reader, 2048) assert.FatalError(t, err) - // nolint:gosec // tests minimum size of the key + //nolint:gosec // tests minimum size of the key rsa1024, err := rsa.GenerateKey(rand.Reader, 1024) assert.FatalError(t, err) @@ -461,8 +462,8 @@ func TestJWK_AuthorizeSSHSign(t *testing.T) { return } if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.Nil(t, got) } else if assert.NotNil(t, got) { @@ -626,8 +627,8 @@ func TestJWK_AuthorizeSSHRevoke(t *testing.T) { tc := tt(t) if err := tc.p.AuthorizeSSHRevoke(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 28be0d5c..e970616d 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -15,6 +15,7 @@ import ( "go.step.sm/crypto/pemutil" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" "github.com/smallstep/certificates/errs" ) @@ -93,7 +94,6 @@ func (p *K8sSA) GetEncryptedKey() (string, string, bool) { // Init initializes and validates the fields of a K8sSA type. func (p *K8sSA) Init(config Config) (err error) { - switch { case p.Type == "": return errors.New("provisioner type cannot be empty") @@ -243,6 +243,7 @@ func (p *K8sSA) AuthorizeSign(ctx context.Context, token string) ([]SignOption, defaultPublicKeyValidator{}, newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), + p.ctl.newWebhookController(data, linkedca.Webhook_X509), }, nil } @@ -288,6 +289,8 @@ func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOptio &sshCertDefaultValidator{}, // Ensure that all principal names are allowed newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), p.ctl.getPolicy().getSSHUser()), + // Call webhooks + p.ctl.newWebhookController(data, linkedca.Webhook_SSH), ), nil } diff --git a/authority/provisioner/k8sSA_test.go b/authority/provisioner/k8sSA_test.go index 2458babb..48581c2d 100644 --- a/authority/provisioner/k8sSA_test.go +++ b/authority/provisioner/k8sSA_test.go @@ -118,8 +118,8 @@ func TestK8sSA_authorizeToken(t *testing.T) { tc := tt(t) if claims, err := tc.p.authorizeToken(tc.token, testAudiences.Sign); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -167,8 +167,8 @@ func TestK8sSA_AuthorizeRevoke(t *testing.T) { t.Run(name, func(t *testing.T) { tc := tt(t) if err := tc.p.AuthorizeRevoke(context.Background(), tc.token); err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) @@ -223,8 +223,8 @@ func TestK8sSA_AuthorizeRenew(t *testing.T) { t.Run(name, func(t *testing.T) { tc := tt(t) if err := tc.p.AuthorizeRenew(context.Background(), tc.cert); err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) @@ -272,8 +272,8 @@ func TestK8sSA_AuthorizeSign(t *testing.T) { tc := tt(t) if opts, err := tc.p.AuthorizeSign(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -297,11 +297,13 @@ func TestK8sSA_AuthorizeSign(t *testing.T) { assert.Equals(t, v.max, tc.p.ctl.Claimer.MaxTLSCertDuration()) case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) + case *WebhookController: + assert.Len(t, 0, v.webhooks) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } } - assert.Equals(t, 7, len(opts)) + assert.Equals(t, 8, len(opts)) } } } @@ -360,15 +362,15 @@ func TestK8sSA_AuthorizeSSHSign(t *testing.T) { tc := tt(t) if opts, err := tc.p.AuthorizeSSHSign(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { if assert.Nil(t, tc.err) { if assert.NotNil(t, opts) { - assert.Len(t, 8, opts) + assert.Len(t, 9, opts) for _, o := range opts { switch v := o.(type) { case Interface: @@ -384,6 +386,8 @@ func TestK8sSA_AuthorizeSSHSign(t *testing.T) { case *sshNamePolicyValidator: assert.Equals(t, nil, v.userPolicyEngine) assert.Equals(t, nil, v.hostPolicyEngine) + case *WebhookController: + assert.Len(t, 0, v.webhooks) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } diff --git a/authority/provisioner/keystore.go b/authority/provisioner/keystore.go index 8b276a75..e74a6b8a 100644 --- a/authority/provisioner/keystore.go +++ b/authority/provisioner/keystore.go @@ -85,14 +85,14 @@ func (ks *keyStore) reload() { // 0 it will randomly rotate between 0-12 hours, but every time we call to Get // it will automatically rotate. func (ks *keyStore) nextReloadDuration(age time.Duration) time.Duration { - n := rand.Int63n(int64(ks.jitter)) // nolint:gosec // not used for cryptographic security + n := rand.Int63n(int64(ks.jitter)) //nolint:gosec // not used for cryptographic security age -= time.Duration(n) return abs(age) } func getKeysFromJWKsURI(uri string) (jose.JSONWebKeySet, time.Duration, error) { var keys jose.JSONWebKeySet - resp, err := http.Get(uri) // nolint:gosec // openid-configuration jwks_uri + resp, err := http.Get(uri) //nolint:gosec // openid-configuration jwks_uri if err != nil { return keys, 0, errors.Wrapf(err, "failed to connect to %s", uri) } diff --git a/authority/provisioner/nebula.go b/authority/provisioner/nebula.go index cde5857c..02762a0a 100644 --- a/authority/provisioner/nebula.go +++ b/authority/provisioner/nebula.go @@ -15,6 +15,7 @@ import ( "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x25519" "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" "golang.org/x/crypto/ssh" "github.com/smallstep/certificates/errs" @@ -164,6 +165,7 @@ func (p *Nebula) AuthorizeSign(ctx context.Context, token string) ([]SignOption, defaultPublicKeyValidator{}, newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), + p.ctl.newWebhookController(data, linkedca.Webhook_X509), }, nil } @@ -262,6 +264,8 @@ func (p *Nebula) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOpti &sshCertDefaultValidator{}, // Ensure that all principal names are allowed newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil), + // Call webhooks + p.ctl.newWebhookController(data, linkedca.Webhook_SSH), ), nil } diff --git a/authority/provisioner/noop.go b/authority/provisioner/noop.go index 9ccd0c8c..bba64eb8 100644 --- a/authority/provisioner/noop.go +++ b/authority/provisioner/noop.go @@ -54,6 +54,7 @@ func (p *noop) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption } func (p *noop) AuthorizeSSHRenew(ctx context.Context, token string) (*ssh.Certificate, error) { + //nolint:nilnil // fine for noop return nil, nil } diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index bb3745b7..3840a4a8 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -16,6 +16,7 @@ import ( "go.step.sm/crypto/jose" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" "github.com/smallstep/certificates/errs" ) @@ -356,6 +357,8 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e defaultPublicKeyValidator{}, newValidityValidator(o.ctl.Claimer.MinTLSCertDuration(), o.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(o.ctl.getPolicy().getX509()), + // webhooks + o.ctl.newWebhookController(data, linkedca.Webhook_X509), }, nil } @@ -460,6 +463,8 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption &sshCertDefaultValidator{}, // Ensure that all principal names are allowed newSSHNamePolicyValidator(o.ctl.getPolicy().getSSHHost(), o.ctl.getPolicy().getSSHUser()), + // Call webhooks + o.ctl.newWebhookController(data, linkedca.Webhook_SSH), ), nil } @@ -479,7 +484,7 @@ func (o *OIDC) AuthorizeSSHRevoke(ctx context.Context, token string) error { } func getAndDecode(uri string, v interface{}) error { - resp, err := http.Get(uri) // nolint:gosec // openid-configuration uri + resp, err := http.Get(uri) //nolint:gosec // openid-configuration uri if err != nil { return errors.Wrapf(err, "failed to connect to %s", uri) } diff --git a/authority/provisioner/oidc_test.go b/authority/provisioner/oidc_test.go index 7f80315f..083799f6 100644 --- a/authority/provisioner/oidc_test.go +++ b/authority/provisioner/oidc_test.go @@ -247,8 +247,8 @@ func TestOIDC_authorizeToken(t *testing.T) { return } if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.Nil(t, got) } else { @@ -318,12 +318,12 @@ func TestOIDC_AuthorizeSign(t *testing.T) { return } if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.Nil(t, got) } else if assert.NotNil(t, got) { - assert.Equals(t, 7, len(got)) + assert.Equals(t, 8, len(got)) for _, o := range got { switch v := o.(type) { case *OIDC: @@ -343,6 +343,8 @@ func TestOIDC_AuthorizeSign(t *testing.T) { assert.Equals(t, string(v), "name@smallstep.com") case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) + case *WebhookController: + assert.Len(t, 0, v.webhooks) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } @@ -406,8 +408,8 @@ func TestOIDC_AuthorizeRevoke(t *testing.T) { t.Errorf("OIDC.Authorize() error = %v, wantErr %v", err, tt.wantErr) return } else if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) } }) @@ -452,8 +454,8 @@ func TestOIDC_AuthorizeRenew(t *testing.T) { if (err != nil) != tt.wantErr { t.Errorf("OIDC.AuthorizeRenew() error = %v, wantErr %v", err, tt.wantErr) } else if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) } }) @@ -540,7 +542,7 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) { pub := key.Public().Key rsa2048, err := rsa.GenerateKey(rand.Reader, 2048) assert.FatalError(t, err) - // nolint:gosec // tests minimum size of the key + //nolint:gosec // tests minimum size of the key rsa1024, err := rsa.GenerateKey(rand.Reader, 1024) assert.FatalError(t, err) @@ -614,8 +616,8 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) { return } if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) assert.Nil(t, got) } else if assert.NotNil(t, got) { @@ -682,8 +684,8 @@ func TestOIDC_AuthorizeSSHRevoke(t *testing.T) { if (err != nil) != tt.wantErr { t.Errorf("OIDC.AuthorizeSSHRevoke() error = %v, wantErr %v", err, tt.wantErr) } else if err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tt.code) } }) diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index f5c919b4..702666a4 100644 --- a/authority/provisioner/options.go +++ b/authority/provisioner/options.go @@ -29,6 +29,9 @@ func (fn certificateOptionsFunc) Options(so SignOptions) []x509util.Option { type Options struct { X509 *X509Options `json:"x509,omitempty"` SSH *SSHOptions `json:"ssh,omitempty"` + + // Webhooks is a list of webhooks that can augment template data + Webhooks []*Webhook `json:"webhooks,omitempty"` } // GetX509Options returns the X.509 options. @@ -47,6 +50,14 @@ func (o *Options) GetSSHOptions() *SSHOptions { return o.SSH } +// GetWebhooks returns the webhooks options. +func (o *Options) GetWebhooks() []*Webhook { + if o == nil { + return nil + } + return o.Webhooks +} + // X509Options contains specific options for X.509 certificates. type X509Options struct { // Template contains a X.509 certificate template. It can be a JSON template diff --git a/authority/provisioner/options_test.go b/authority/provisioner/options_test.go index 652fff73..405ec8b7 100644 --- a/authority/provisioner/options_test.go +++ b/authority/provisioner/options_test.go @@ -68,6 +68,36 @@ func TestOptions_GetSSHOptions(t *testing.T) { } } +func TestOptions_GetWebhooks(t *testing.T) { + type fields struct { + o *Options + } + tests := []struct { + name string + fields fields + want []*Webhook + }{ + {"ok", fields{&Options{Webhooks: []*Webhook{ + {Name: "foo"}, + {Name: "bar"}, + }}}, + []*Webhook{ + {Name: "foo"}, + {Name: "bar"}, + }, + }, + {"nil", fields{&Options{}}, nil}, + {"nilOptions", fields{nil}, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.fields.o.GetWebhooks(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Options.GetWebhooks() = %v, want %v", got, tt.want) + } + }) + } +} + func TestProvisionerX509Options_HasTemplate(t *testing.T) { type fields struct { Template string @@ -254,7 +284,7 @@ func TestCustomTemplateOptions(t *testing.T) { } func Test_unsafeParseSigned(t *testing.T) { - // nolint:gosec // no credentials here + //nolint:gosec // no credentials here okToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJqYW5lQGRvZS5jb20iLCJpc3MiOiJodHRwczovL2RvZS5jb20iLCJqdGkiOiI4ZmYzMjQ4MS1mZDVmLTRlMmUtOTZkZi05MDhjMTI3Yzg1ZjciLCJpYXQiOjE1OTUzNjAwMjgsImV4cCI6MTU5NTM2MzYyOH0.aid8UuhFucJOFHXaob9zpNtVvhul9ulTGsA52mU6XIw" type args struct { s string diff --git a/authority/provisioner/policy.go b/authority/provisioner/policy.go index 95ef4163..caf8c782 100644 --- a/authority/provisioner/policy.go +++ b/authority/provisioner/policy.go @@ -9,8 +9,8 @@ type policyEngine struct { } func newPolicyEngine(options *Options) (*policyEngine, error) { - if options == nil { + //nolint:nilnil // legacy return nil, nil } diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 29d44c1c..9d65d585 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "encoding/json" stderrors "errors" + "net/http" "net/url" "strings" @@ -222,6 +223,8 @@ type Config struct { // AuthorizeSSHRenewFunc is a function that returns nil if a given SSH // certificate can be renewed. AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc + // WebhookClient is an http client to use in webhook request + WebhookClient *http.Client } type provisioner struct { diff --git a/authority/provisioner/provisioner_test.go b/authority/provisioner/provisioner_test.go index 9678a20b..65fb8e1d 100644 --- a/authority/provisioner/provisioner_test.go +++ b/authority/provisioner/provisioner_test.go @@ -241,9 +241,10 @@ func TestUnimplementedMethods(t *testing.T) { default: t.Errorf("unexpected method %s", tt.method) } - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), http.StatusUnauthorized) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), http.StatusUnauthorized) + } assert.Equals(t, err.Error(), msg) }) } diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index c49c993e..0f27b206 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -5,6 +5,7 @@ import ( "time" "github.com/pkg/errors" + "go.step.sm/linkedca" ) // SCEP is the SCEP provisioner type, an entity that can authorize the @@ -128,6 +129,7 @@ func (s *SCEP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e newPublicKeyMinimumLengthValidator(s.MinimumPublicKeyLength), newValidityValidator(s.ctl.Claimer.MinTLSCertDuration(), s.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(s.ctl.getPolicy().getX509()), + s.ctl.newWebhookController(nil, linkedca.Webhook_X509), }, nil } diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 8a0363a6..bc0d88ff 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -310,7 +310,6 @@ func (v profileDefaultDuration) Modify(cert *x509.Certificate, so SignOptions) e if notBefore.IsZero() { notBefore = now() backdate = -1 * so.Backdate - } notAfter := so.NotAfter.RelativeTime(notBefore) if notAfter.IsZero() { diff --git a/authority/provisioner/sign_ssh_options_test.go b/authority/provisioner/sign_ssh_options_test.go index 28a35639..1993295b 100644 --- a/authority/provisioner/sign_ssh_options_test.go +++ b/authority/provisioner/sign_ssh_options_test.go @@ -287,7 +287,7 @@ func Test_sshCertTypeModifier_Modify(t *testing.T) { t.Run(name, func(t *testing.T) { tc := run() if assert.Nil(t, tc.modifier.Modify(tc.cert, SignSSHOptions{})) { - assert.Equals(t, tc.cert.CertType, uint32(tc.expected)) + assert.Equals(t, tc.cert.CertType, tc.expected) } }) } diff --git a/authority/provisioner/ssh_test.go b/authority/provisioner/ssh_test.go index b86945a3..6ad71459 100644 --- a/authority/provisioner/ssh_test.go +++ b/authority/provisioner/ssh_test.go @@ -2,6 +2,7 @@ package provisioner import ( "crypto" + "errors" "fmt" "net/http" "reflect" @@ -68,6 +69,8 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si if err := o.Valid(opts); err != nil { return nil, err } + // call webhooks + case *WebhookController: default: return nil, fmt.Errorf("signSSH: invalid extra option type %T", o) } @@ -84,9 +87,10 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si // Create certificate from template. certificate, err := sshutil.NewCertificate(cr, certOptions...) if err != nil { - if _, ok := err.(*sshutil.TemplateError); ok { - return nil, errs.NewErr(http.StatusBadRequest, err, - errs.WithMessage(err.Error()), + var templErr *sshutil.TemplateError + if errors.As(err, &templErr) { + return nil, errs.NewErr(http.StatusBadRequest, templErr, + errs.WithMessage(templErr.Error()), errs.WithKeyVal("signOptions", signOpts), ) } diff --git a/authority/provisioner/sshpop_test.go b/authority/provisioner/sshpop_test.go index 1e026883..ae75b349 100644 --- a/authority/provisioner/sshpop_test.go +++ b/authority/provisioner/sshpop_test.go @@ -218,9 +218,10 @@ func TestSSHPOP_authorizeToken(t *testing.T) { t.Run(name, func(t *testing.T) { tc := tt(t) if claims, err := tc.p.authorizeToken(tc.token, testAudiences.Sign, true); err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -289,9 +290,10 @@ func TestSSHPOP_AuthorizeSSHRevoke(t *testing.T) { t.Run(name, func(t *testing.T) { tc := tt(t) if err := tc.p.AuthorizeSSHRevoke(context.Background(), tc.token); err != nil { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } if assert.NotNil(t, tc.err) { assert.HasPrefix(t, err.Error(), tc.err.Error()) } @@ -370,9 +372,10 @@ func TestSSHPOP_AuthorizeSSHRenew(t *testing.T) { tc := tt(t) if cert, err := tc.p.AuthorizeSSHRenew(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { @@ -452,9 +455,10 @@ func TestSSHPOP_AuthorizeSSHRekey(t *testing.T) { tc := tt(t) if cert, opts, err := tc.p.AuthorizeSSHRekey(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { diff --git a/authority/provisioner/testdata/certs/foo.crt b/authority/provisioner/testdata/certs/foo.crt new file mode 100644 index 00000000..eb06f218 --- /dev/null +++ b/authority/provisioner/testdata/certs/foo.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICIDCCAcagAwIBAgIQTL7pKDl8mFzRziotXbgjEjAKBggqhkjOPQQDAjAnMSUw +IwYDVQQDExxFeGFtcGxlIEluYy4gSW50ZXJtZWRpYXRlIENBMB4XDTE5MDMyMjIy +MjkyOVoXDTE5MDMyMzIyMjkyOVowHDEaMBgGA1UEAxMRZm9vLnNtYWxsc3RlcC5j +b20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQbptfDonFaeUPiTr52wl9r3dcz +greolwDRmsgyFgnr1EuKH56WRcgH1gjfL0pybFlO3PdgBukR4u+sveq343OAo4He +MIHbMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH +AwIwHQYDVR0OBBYEFP9pHiVlsx5mr4L2QirOb1G9Mo4jMB8GA1UdIwQYMBaAFKEe +9IdMyaHdURMjoJce7FN9HC9wMBwGA1UdEQQVMBOCEWZvby5zbWFsbHN0ZXAuY29t +MEwGDCsGAQQBgqRkxihAAQQ8MDoCAQEECHN0ZXAtY2xpBCs0VUVMSng4ZTBhUzlt +MENIM2ZaMEVCN0Q1YVVQSUNiNzU5ekFMSEZlanZjMAoGCCqGSM49BAMCA0gAMEUC +IDxtNo1BX/4Sbf/+k1n+v//kh8ETr3clPvhjcyfvBIGTAiEAiT0kvbkPdCCnmHIw +lhpgBwT5YReZzBwIYXyKyJXc07M= +-----END CERTIFICATE----- diff --git a/authority/provisioner/testdata/secrets/foo.key b/authority/provisioner/testdata/secrets/foo.key new file mode 100644 index 00000000..b1b63324 --- /dev/null +++ b/authority/provisioner/testdata/secrets/foo.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJmnxm3N/ahRA2PWeZhRGJUKPU1lI44WcE4P1bynIim6oAoGCCqGSM49 +AwEHoUQDQgAEG6bXw6JxWnlD4k6+dsJfa93XM4K3qJcA0ZrIMhYJ69RLih+elkXI +B9YI3y9KcmxZTtz3YAbpEeLvrL3qt+NzgA== +-----END EC PRIVATE KEY----- diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index 265c7b08..f0e6949f 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -100,7 +100,7 @@ func generateJSONWebKey() (*jose.JSONWebKey, error) { if err != nil { return nil, err } - jwk.KeyID = string(hex.EncodeToString(fp)) + jwk.KeyID = hex.EncodeToString(fp) return jwk, nil } @@ -449,7 +449,7 @@ func generateAWSWithServer() (*AWS, *httptest.Server, error) { if err != nil { return nil, nil, errors.Wrap(err, "error signing document") } - // nolint:gosec // tests minimum size of the key + //nolint:gosec // tests minimum size of the key token := "AQAEAEEO9-7Z88ewKFpboZuDlFYWz9A3AN-wMOVzjEhfAyXW31BvVw==" srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { diff --git a/authority/provisioner/webhook.go b/authority/provisioner/webhook.go new file mode 100644 index 00000000..ea02da35 --- /dev/null +++ b/authority/provisioner/webhook.go @@ -0,0 +1,209 @@ +package provisioner + +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "log" + "net/http" + "text/template" + "time" + + "github.com/pkg/errors" + "github.com/smallstep/certificates/templates" + "github.com/smallstep/certificates/webhook" + "go.step.sm/linkedca" +) + +var ErrWebhookDenied = errors.New("webhook server did not allow request") + +type WebhookSetter interface { + SetWebhook(string, any) +} + +type WebhookController struct { + client *http.Client + webhooks []*Webhook + certType linkedca.Webhook_CertType + TemplateData WebhookSetter +} + +// Enrich fetches data from remote servers and adds returned data to the +// templateData +func (wc *WebhookController) Enrich(req *webhook.RequestBody) error { + if wc == nil { + return nil + } + for _, wh := range wc.webhooks { + if wh.Kind != linkedca.Webhook_ENRICHING.String() { + continue + } + if !wc.isCertTypeOK(wh) { + continue + } + resp, err := wh.Do(wc.client, req, wc.TemplateData) + if err != nil { + return err + } + if !resp.Allow { + return ErrWebhookDenied + } + wc.TemplateData.SetWebhook(wh.Name, resp.Data) + } + return nil +} + +// Authorize checks that all remote servers allow the request +func (wc *WebhookController) Authorize(req *webhook.RequestBody) error { + if wc == nil { + return nil + } + for _, wh := range wc.webhooks { + if wh.Kind != linkedca.Webhook_AUTHORIZING.String() { + continue + } + if !wc.isCertTypeOK(wh) { + continue + } + resp, err := wh.Do(wc.client, req, wc.TemplateData) + if err != nil { + return err + } + if !resp.Allow { + return ErrWebhookDenied + } + } + return nil +} + +func (wc *WebhookController) isCertTypeOK(wh *Webhook) bool { + if wc.certType == linkedca.Webhook_ALL { + return true + } + if wh.CertType == linkedca.Webhook_ALL.String() || wh.CertType == "" { + return true + } + return wc.certType.String() == wh.CertType +} + +type Webhook struct { + ID string `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + Kind string `json:"kind"` + DisableTLSClientAuth bool `json:"disableTLSClientAuth,omitempty"` + CertType string `json:"certType"` + Secret string `json:"-"` + BearerToken string `json:"-"` + BasicAuth struct { + Username string + Password string + } `json:"-"` +} + +func (w *Webhook) Do(client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) { + tmpl, err := template.New("url").Funcs(templates.StepFuncMap()).Parse(w.URL) + if err != nil { + return nil, err + } + buf := &bytes.Buffer{} + if err := tmpl.Execute(buf, data); err != nil { + return nil, err + } + url := buf.String() + + /* + Sending the token to the webhook server is a security risk. A K8sSA + token can be reused multiple times. The webhook can misuse it to get + fake certificates. A webhook can misuse any other token to get its own + certificate before responding. + switch tmpl := data.(type) { + case x509util.TemplateData: + reqBody.Token = tmpl[x509util.TokenKey] + case sshutil.TemplateData: + reqBody.Token = tmpl[sshutil.TokenKey] + } + */ + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + reqBody.Timestamp = time.Now() + + reqBytes, err := json.Marshal(reqBody) + if err != nil { + return nil, err + } + + retries := 1 +retry: + + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(reqBytes)) + if err != nil { + return nil, err + } + + secret, err := base64.StdEncoding.DecodeString(w.Secret) + if err != nil { + return nil, err + } + sig := hmac.New(sha256.New, secret).Sum(reqBytes) + req.Header.Set("X-Smallstep-Signature", hex.EncodeToString(sig)) + req.Header.Set("X-Smallstep-Webhook-ID", w.ID) + + if w.BearerToken != "" { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", w.BearerToken)) + } else if w.BasicAuth.Username != "" || w.BasicAuth.Password != "" { + req.SetBasicAuth(w.BasicAuth.Username, w.BasicAuth.Password) + } + + if w.DisableTLSClientAuth { + transport, ok := client.Transport.(*http.Transport) + if !ok { + return nil, errors.New("client transport is not a *http.Transport") + } + transport = transport.Clone() + tlsConfig := transport.TLSClientConfig.Clone() + tlsConfig.GetClientCertificate = nil + tlsConfig.Certificates = nil + transport.TLSClientConfig = tlsConfig + client = &http.Client{ + Transport: transport, + } + } + resp, err := client.Do(req) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + return nil, err + } else if retries > 0 { + retries-- + time.Sleep(time.Second) + goto retry + } + return nil, err + } + defer func() { + if err := resp.Body.Close(); err != nil { + log.Printf("Failed to close body of response from %s", w.URL) + } + }() + if resp.StatusCode >= 500 && retries > 0 { + retries-- + time.Sleep(time.Second) + goto retry + } + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("Webhook server responded with %d", resp.StatusCode) + } + + respBody := &webhook.ResponseBody{} + if err := json.NewDecoder(resp.Body).Decode(respBody); err != nil { + return nil, err + } + + return respBody, nil +} diff --git a/authority/provisioner/webhook_test.go b/authority/provisioner/webhook_test.go new file mode 100644 index 00000000..a7895638 --- /dev/null +++ b/authority/provisioner/webhook_test.go @@ -0,0 +1,473 @@ +package provisioner + +import ( + "crypto/hmac" + "crypto/sha256" + "crypto/tls" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/pkg/errors" + "github.com/smallstep/assert" + "github.com/smallstep/certificates/webhook" + "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" +) + +func TestWebhookController_isCertTypeOK(t *testing.T) { + type test struct { + wc *WebhookController + wh *Webhook + want bool + } + tests := map[string]test{ + "all/all": { + wc: &WebhookController{certType: linkedca.Webhook_ALL}, + wh: &Webhook{CertType: linkedca.Webhook_ALL.String()}, + want: true, + }, + "all/x509": { + wc: &WebhookController{certType: linkedca.Webhook_ALL}, + wh: &Webhook{CertType: linkedca.Webhook_X509.String()}, + want: true, + }, + "all/ssh": { + wc: &WebhookController{certType: linkedca.Webhook_ALL}, + wh: &Webhook{CertType: linkedca.Webhook_SSH.String()}, + want: true, + }, + `all/""`: { + wc: &WebhookController{certType: linkedca.Webhook_ALL}, + wh: &Webhook{}, + want: true, + }, + "x509/all": { + wc: &WebhookController{certType: linkedca.Webhook_X509}, + wh: &Webhook{CertType: linkedca.Webhook_ALL.String()}, + want: true, + }, + "x509/x509": { + wc: &WebhookController{certType: linkedca.Webhook_X509}, + wh: &Webhook{CertType: linkedca.Webhook_X509.String()}, + want: true, + }, + "x509/ssh": { + wc: &WebhookController{certType: linkedca.Webhook_X509}, + wh: &Webhook{CertType: linkedca.Webhook_SSH.String()}, + want: false, + }, + `x509/""`: { + wc: &WebhookController{certType: linkedca.Webhook_X509}, + wh: &Webhook{}, + want: true, + }, + "ssh/all": { + wc: &WebhookController{certType: linkedca.Webhook_SSH}, + wh: &Webhook{CertType: linkedca.Webhook_ALL.String()}, + want: true, + }, + "ssh/x509": { + wc: &WebhookController{certType: linkedca.Webhook_SSH}, + wh: &Webhook{CertType: linkedca.Webhook_X509.String()}, + want: false, + }, + "ssh/ssh": { + wc: &WebhookController{certType: linkedca.Webhook_SSH}, + wh: &Webhook{CertType: linkedca.Webhook_SSH.String()}, + want: true, + }, + `ssh/""`: { + wc: &WebhookController{certType: linkedca.Webhook_SSH}, + wh: &Webhook{}, + want: true, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + assert.Equals(t, test.want, test.wc.isCertTypeOK(test.wh)) + }) + } +} + +func TestWebhookController_Enrich(t *testing.T) { + type test struct { + ctl *WebhookController + req *webhook.RequestBody + responses []*webhook.ResponseBody + expectErr bool + expectTemplateData any + } + tests := map[string]test{ + "ok/no enriching webhooks": { + ctl: &WebhookController{ + client: http.DefaultClient, + webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}}, + TemplateData: nil, + }, + req: &webhook.RequestBody{}, + responses: nil, + expectErr: false, + expectTemplateData: nil, + }, + "ok/one webhook": { + ctl: &WebhookController{ + client: http.DefaultClient, + webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}}, + TemplateData: x509util.TemplateData{}, + }, + req: &webhook.RequestBody{}, + responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}}, + expectErr: false, + expectTemplateData: x509util.TemplateData{"Webhooks": map[string]any{"people": map[string]any{"role": "bar"}}}, + }, + "ok/two webhooks": { + ctl: &WebhookController{ + client: http.DefaultClient, + webhooks: []*Webhook{ + {Name: "people", Kind: "ENRICHING"}, + {Name: "devices", Kind: "ENRICHING"}, + }, + TemplateData: x509util.TemplateData{}, + }, + req: &webhook.RequestBody{}, + responses: []*webhook.ResponseBody{ + {Allow: true, Data: map[string]any{"role": "bar"}}, + {Allow: true, Data: map[string]any{"serial": "123"}}, + }, + expectErr: false, + expectTemplateData: x509util.TemplateData{ + "Webhooks": map[string]any{ + "devices": map[string]any{"serial": "123"}, + "people": map[string]any{"role": "bar"}, + }, + }, + }, + "ok/x509 only": { + ctl: &WebhookController{ + client: http.DefaultClient, + webhooks: []*Webhook{ + {Name: "people", Kind: "ENRICHING", CertType: linkedca.Webhook_SSH.String()}, + {Name: "devices", Kind: "ENRICHING"}, + }, + TemplateData: x509util.TemplateData{}, + certType: linkedca.Webhook_X509, + }, + req: &webhook.RequestBody{}, + responses: []*webhook.ResponseBody{ + {Allow: true, Data: map[string]any{"role": "bar"}}, + {Allow: true, Data: map[string]any{"serial": "123"}}, + }, + expectErr: false, + expectTemplateData: x509util.TemplateData{ + "Webhooks": map[string]any{ + "devices": map[string]any{"serial": "123"}, + }, + }, + }, + "deny": { + ctl: &WebhookController{ + client: http.DefaultClient, + webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}}, + TemplateData: x509util.TemplateData{}, + }, + req: &webhook.RequestBody{}, + responses: []*webhook.ResponseBody{{Allow: false}}, + expectErr: true, + expectTemplateData: x509util.TemplateData{}, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + for i, wh := range test.ctl.webhooks { + var j = i + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := json.NewEncoder(w).Encode(test.responses[j]) + assert.FatalError(t, err) + })) + // nolint: gocritic // defer in loop isn't a memory leak + defer ts.Close() + wh.URL = ts.URL + } + + err := test.ctl.Enrich(test.req) + if (err != nil) != test.expectErr { + t.Fatalf("Got err %v, want %v", err, test.expectErr) + } + assert.Equals(t, test.expectTemplateData, test.ctl.TemplateData) + }) + } +} + +func TestWebhookController_Authorize(t *testing.T) { + type test struct { + ctl *WebhookController + req *webhook.RequestBody + responses []*webhook.ResponseBody + expectErr bool + } + tests := map[string]test{ + "ok/no enriching webhooks": { + ctl: &WebhookController{ + client: http.DefaultClient, + webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}}, + }, + req: &webhook.RequestBody{}, + responses: nil, + expectErr: false, + }, + "ok": { + ctl: &WebhookController{ + client: http.DefaultClient, + webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}}, + }, + req: &webhook.RequestBody{}, + responses: []*webhook.ResponseBody{{Allow: true}}, + expectErr: false, + }, + "ok/ssh only": { + ctl: &WebhookController{ + client: http.DefaultClient, + webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING", CertType: linkedca.Webhook_X509.String()}}, + certType: linkedca.Webhook_SSH, + }, + req: &webhook.RequestBody{}, + responses: []*webhook.ResponseBody{{Allow: false}}, + expectErr: false, + }, + "deny": { + ctl: &WebhookController{ + client: http.DefaultClient, + webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}}, + }, + req: &webhook.RequestBody{}, + responses: []*webhook.ResponseBody{{Allow: false}}, + expectErr: true, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + for i, wh := range test.ctl.webhooks { + var j = i + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := json.NewEncoder(w).Encode(test.responses[j]) + assert.FatalError(t, err) + })) + // nolint: gocritic // defer in loop isn't a memory leak + defer ts.Close() + wh.URL = ts.URL + } + + err := test.ctl.Authorize(test.req) + if (err != nil) != test.expectErr { + t.Fatalf("Got err %v, want %v", err, test.expectErr) + } + }) + } +} + +func TestWebhook_Do(t *testing.T) { + csr := parseCertificateRequest(t, "testdata/certs/ecdsa.csr") + type test struct { + webhook Webhook + dataArg any + webhookResponse webhook.ResponseBody + expectPath string + errStatusCode int + serverErrMsg string + expectErr error + // expectToken any + } + tests := map[string]test{ + "ok": { + webhook: Webhook{ + ID: "abc123", + Secret: "c2VjcmV0Cg==", + }, + webhookResponse: webhook.ResponseBody{ + Data: map[string]interface{}{"role": "dba"}, + }, + }, + "ok/bearer": { + webhook: Webhook{ + ID: "abc123", + Secret: "c2VjcmV0Cg==", + BearerToken: "mytoken", + }, + webhookResponse: webhook.ResponseBody{ + Data: map[string]interface{}{"role": "dba"}, + }, + }, + "ok/basic": { + webhook: Webhook{ + ID: "abc123", + Secret: "c2VjcmV0Cg==", + BasicAuth: struct { + Username string + Password string + }{ + Username: "myuser", + Password: "mypass", + }, + }, + webhookResponse: webhook.ResponseBody{ + Data: map[string]interface{}{"role": "dba"}, + }, + }, + "ok/templated-url": { + webhook: Webhook{ + ID: "abc123", + // scheme, host, port will come from test server + URL: "/users/{{ .username }}?region={{ .region }}", + Secret: "c2VjcmV0Cg==", + }, + dataArg: map[string]interface{}{"username": "areed", "region": "central"}, + webhookResponse: webhook.ResponseBody{ + Data: map[string]interface{}{"role": "dba"}, + }, + expectPath: "/users/areed?region=central", + }, + /* + "ok/token from ssh template": { + webhook: Webhook{ + ID: "abc123", + Secret: "c2VjcmV0Cg==", + }, + webhookResponse: webhook.ResponseBody{ + Data: map[string]interface{}{"role": "dba"}, + }, + dataArg: sshutil.TemplateData{sshutil.TokenKey: "token"}, + expectToken: "token", + }, + "ok/token from x509 template": { + webhook: Webhook{ + ID: "abc123", + Secret: "c2VjcmV0Cg==", + }, + webhookResponse: webhook.ResponseBody{ + Data: map[string]interface{}{"role": "dba"}, + }, + dataArg: x509util.TemplateData{sshutil.TokenKey: "token"}, + expectToken: "token", + }, + */ + "ok/allow": { + webhook: Webhook{ + ID: "abc123", + Secret: "c2VjcmV0Cg==", + }, + webhookResponse: webhook.ResponseBody{ + Allow: true, + }, + }, + "fail/404": { + webhook: Webhook{ + ID: "abc123", + Secret: "c2VjcmV0Cg==", + }, + webhookResponse: webhook.ResponseBody{ + Data: map[string]interface{}{"role": "dba"}, + }, + errStatusCode: 404, + serverErrMsg: "item not found", + expectErr: errors.New("Webhook server responded with 404"), + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + id := r.Header.Get("X-Smallstep-Webhook-ID") + assert.Equals(t, tc.webhook.ID, id) + + sig, err := hex.DecodeString(r.Header.Get("X-Smallstep-Signature")) + assert.FatalError(t, err) + + body, err := io.ReadAll(r.Body) + assert.FatalError(t, err) + + secret, err := base64.StdEncoding.DecodeString(tc.webhook.Secret) + assert.FatalError(t, err) + mac := hmac.New(sha256.New, secret).Sum(body) + assert.True(t, hmac.Equal(sig, mac)) + + switch { + case tc.webhook.BearerToken != "": + ah := fmt.Sprintf("Bearer %s", tc.webhook.BearerToken) + assert.Equals(t, ah, r.Header.Get("Authorization")) + case tc.webhook.BasicAuth.Username != "" || tc.webhook.BasicAuth.Password != "": + whReq, err := http.NewRequest("", "", http.NoBody) + assert.FatalError(t, err) + whReq.SetBasicAuth(tc.webhook.BasicAuth.Username, tc.webhook.BasicAuth.Password) + ah := whReq.Header.Get("Authorization") + assert.Equals(t, ah, whReq.Header.Get("Authorization")) + default: + assert.Equals(t, "", r.Header.Get("Authorization")) + } + + if tc.expectPath != "" { + assert.Equals(t, tc.expectPath, r.URL.Path+"?"+r.URL.RawQuery) + } + + if tc.errStatusCode != 0 { + http.Error(w, tc.serverErrMsg, tc.errStatusCode) + return + } + + reqBody := new(webhook.RequestBody) + err = json.Unmarshal(body, reqBody) + assert.FatalError(t, err) + // assert.Equals(t, tc.expectToken, reqBody.Token) + + err = json.NewEncoder(w).Encode(tc.webhookResponse) + assert.FatalError(t, err) + })) + defer ts.Close() + + tc.webhook.URL = ts.URL + tc.webhook.URL + + reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr)) + assert.FatalError(t, err) + got, err := tc.webhook.Do(http.DefaultClient, reqBody, tc.dataArg) + if tc.expectErr != nil { + assert.Equals(t, tc.expectErr.Error(), err.Error()) + return + } + assert.FatalError(t, err) + + assert.Equals(t, got, &tc.webhookResponse) + }) + } + + t.Run("disableTLSClientAuth", func(t *testing.T) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("{}")) + })) + ts.TLS.ClientAuth = tls.RequireAnyClientCert + wh := Webhook{ + URL: ts.URL, + } + cert, err := tls.LoadX509KeyPair("testdata/certs/foo.crt", "testdata/secrets/foo.key") + assert.FatalError(t, err) + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{cert}, + } + client := &http.Client{ + Transport: transport, + } + reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr)) + assert.FatalError(t, err) + _, err = wh.Do(client, reqBody, nil) + assert.FatalError(t, err) + + wh.DisableTLSClientAuth = true + _, err = wh.Do(client, reqBody, nil) + assert.Error(t, err) + }) +} diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 9f9a0e4e..e60533b7 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -12,6 +12,7 @@ import ( "go.step.sm/crypto/jose" "go.step.sm/crypto/sshutil" "go.step.sm/crypto/x509util" + "go.step.sm/linkedca" "github.com/smallstep/certificates/errs" ) @@ -245,6 +246,7 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er defaultPublicKeyValidator{}, newValidityValidator(p.ctl.Claimer.MinTLSCertDuration(), p.ctl.Claimer.MaxTLSCertDuration()), newX509NamePolicyValidator(p.ctl.getPolicy().getX509()), + p.ctl.newWebhookController(data, linkedca.Webhook_X509), }, nil } @@ -332,5 +334,7 @@ func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, &sshCertDefaultValidator{}, // Ensure that all principal names are allowed newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), p.ctl.getPolicy().getSSHUser()), + // Call webhooks + p.ctl.newWebhookController(data, linkedca.Webhook_SSH), ), nil } diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index 3bcf30d1..437b7661 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -120,7 +120,7 @@ M46l92gdOozT return ProvisionerValidateTest{ p: p, extraValid: func(p *X5C) error { - // nolint:staticcheck // We don't have a different way to + //nolint:staticcheck // We don't have a different way to // check the number of certificates in the pool. numCerts := len(p.rootPool.Subjects()) if numCerts != 2 { @@ -389,9 +389,10 @@ lgsqsR63is+0YQ== tc := tt(t) if claims, err := tc.p.authorizeToken(tc.token, testAudiences.Sign); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { @@ -460,15 +461,16 @@ func TestX5C_AuthorizeSign(t *testing.T) { tc := tt(t) if opts, err := tc.p.AuthorizeSign(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCoder interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { if assert.Nil(t, tc.err) { if assert.NotNil(t, opts) { - assert.Equals(t, 9, len(opts)) + assert.Equals(t, 10, len(opts)) for _, o := range opts { switch v := o.(type) { case *X5C: @@ -493,6 +495,8 @@ func TestX5C_AuthorizeSign(t *testing.T) { assert.Equals(t, v.max, tc.p.ctl.Claimer.MaxTLSCertDuration()) case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) + case *WebhookController: + assert.Len(t, 0, v.webhooks) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } @@ -545,9 +549,10 @@ func TestX5C_AuthorizeRevoke(t *testing.T) { tc := tt(t) if err := tc.p.AuthorizeRevoke(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { @@ -595,9 +600,10 @@ func TestX5C_AuthorizeRenew(t *testing.T) { NotAfter: now.Add(time.Hour), }); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { @@ -756,9 +762,10 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { tc := tt(t) if opts, err := tc.p.AuthorizeSSHSign(context.Background(), tc.token); err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCoder interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { @@ -794,15 +801,17 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { assert.Equals(t, nil, v.userPolicyEngine) assert.Equals(t, nil, v.hostPolicyEngine) case *sshDefaultPublicKeyValidator, *sshCertDefaultValidator, sshCertificateOptionsFunc: + case *WebhookController: + assert.Len(t, 0, v.webhooks) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } tot++ } if len(tc.claims.Step.SSH.CertType) > 0 { - assert.Equals(t, tot, 11) + assert.Equals(t, tot, 12) } else { - assert.Equals(t, tot, 9) + assert.Equals(t, tot, 10) } } } diff --git a/authority/provisioners.go b/authority/provisioners.go index dcf8de36..24d25caa 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -25,6 +25,60 @@ import ( "github.com/smallstep/certificates/errs" ) +type raProvisioner interface { + RAInfo() *provisioner.RAInfo +} + +type attProvisioner interface { + AttestationData() *provisioner.AttestationData +} + +// wrapProvisioner wraps the given provisioner with RA information and +// attestation data. +func wrapProvisioner(p provisioner.Interface, attData *provisioner.AttestationData) *wrappedProvisioner { + var raInfo *provisioner.RAInfo + if rap, ok := p.(raProvisioner); ok { + raInfo = rap.RAInfo() + } + + return &wrappedProvisioner{ + Interface: p, + attestationData: attData, + raInfo: raInfo, + } +} + +// wrapRAProvisioner wraps the given provisioner with RA information. +func wrapRAProvisioner(p provisioner.Interface, raInfo *provisioner.RAInfo) *wrappedProvisioner { + return &wrappedProvisioner{ + Interface: p, + raInfo: raInfo, + } +} + +// isRAProvisioner returns if the given provisioner is an RA provisioner. +func isRAProvisioner(p provisioner.Interface) bool { + if rap, ok := p.(raProvisioner); ok { + return rap.RAInfo() != nil + } + return false +} + +// wrappedProvisioner implements raProvisioner and attProvisioner. +type wrappedProvisioner struct { + provisioner.Interface + attestationData *provisioner.AttestationData + raInfo *provisioner.RAInfo +} + +func (p *wrappedProvisioner) AttestationData() *provisioner.AttestationData { + return p.attestationData +} + +func (p *wrappedProvisioner) RAInfo() *provisioner.RAInfo { + return p.raInfo +} + // GetEncryptedKey returns the JWE key corresponding to the given kid argument. func (a *Authority) GetEncryptedKey(kid string) (string, error) { a.adminMutex.RLock() @@ -81,6 +135,9 @@ func (a *Authority) unsafeLoadProvisionerFromDatabase(crt *x509.Certificate) (pr } if err == nil && data != nil && data.Provisioner != nil { if p, ok := a.provisioners.Load(data.Provisioner.ID); ok { + if data.RaInfo != nil { + return wrapRAProvisioner(p, data.RaInfo), nil + } return p, nil } } @@ -144,8 +201,8 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner. GetIdentityFunc: a.getIdentityFunc, AuthorizeRenewFunc: a.authorizeRenewFunc, AuthorizeSSHRenewFunc: a.authorizeSSHRenewFunc, + WebhookClient: a.webhookClient, }, nil - } // StoreProvisioner stores a provisioner to the authority. @@ -494,9 +551,63 @@ func optionsToCertificates(p *linkedca.Provisioner) *provisioner.Options { } } } + for _, wh := range p.Webhooks { + whCert := webhookToCertificates(wh) + ops.Webhooks = append(ops.Webhooks, whCert) + } return ops } +func webhookToCertificates(wh *linkedca.Webhook) *provisioner.Webhook { + pwh := &provisioner.Webhook{ + ID: wh.Id, + Name: wh.Name, + URL: wh.Url, + Kind: wh.Kind.String(), + Secret: wh.Secret, + DisableTLSClientAuth: wh.DisableTlsClientAuth, + CertType: wh.CertType.String(), + } + + switch a := wh.GetAuth().(type) { + case *linkedca.Webhook_BearerToken: + pwh.BearerToken = a.BearerToken.BearerToken + case *linkedca.Webhook_BasicAuth: + pwh.BasicAuth.Username = a.BasicAuth.Username + pwh.BasicAuth.Password = a.BasicAuth.Password + } + + return pwh +} + +func provisionerWebhookToLinkedca(pwh *provisioner.Webhook) *linkedca.Webhook { + lwh := &linkedca.Webhook{ + Id: pwh.ID, + Name: pwh.Name, + Url: pwh.URL, + Kind: linkedca.Webhook_Kind(linkedca.Webhook_Kind_value[pwh.Kind]), + Secret: pwh.Secret, + DisableTlsClientAuth: pwh.DisableTLSClientAuth, + CertType: linkedca.Webhook_CertType(linkedca.Webhook_CertType_value[pwh.CertType]), + } + if pwh.BearerToken != "" { + lwh.Auth = &linkedca.Webhook_BearerToken{ + BearerToken: &linkedca.BearerToken{ + BearerToken: pwh.BearerToken, + }, + } + } else if pwh.BasicAuth.Username != "" || pwh.BasicAuth.Password != "" { + lwh.Auth = &linkedca.Webhook_BasicAuth{ + BasicAuth: &linkedca.BasicAuth{ + Username: pwh.BasicAuth.Username, + Password: pwh.BasicAuth.Password, + }, + } + } + + return lwh +} + func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.Duration, err error) { if len(d.Min) > 0 { min, err = provisioner.NewDuration(d.Min) @@ -530,6 +641,7 @@ func durationsToLinkedca(d *provisioner.Duration) string { // certifictes claims type. func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) { if c == nil { + //nolint:nilnil // nil claims do not pose an issue. return nil, nil } @@ -621,12 +733,12 @@ func claimsToLinkedca(c *provisioner.Claims) *linkedca.Claims { return lc } -func provisionerOptionsToLinkedca(p *provisioner.Options) (*linkedca.Template, *linkedca.Template, error) { +func provisionerOptionsToLinkedca(p *provisioner.Options) (*linkedca.Template, *linkedca.Template, []*linkedca.Webhook, error) { var err error var x509Template, sshTemplate *linkedca.Template if p == nil { - return nil, nil, nil + return nil, nil, nil, nil } if p.X509 != nil && p.X509.HasTemplate() { @@ -640,7 +752,7 @@ func provisionerOptionsToLinkedca(p *provisioner.Options) (*linkedca.Template, * } else if p.X509.TemplateFile != "" { filename := step.Abs(p.X509.TemplateFile) if x509Template.Template, err = os.ReadFile(filename); err != nil { - return nil, nil, errors.Wrap(err, "error reading x509 template") + return nil, nil, nil, errors.Wrap(err, "error reading x509 template") } } } @@ -656,12 +768,17 @@ func provisionerOptionsToLinkedca(p *provisioner.Options) (*linkedca.Template, * } else if p.SSH.TemplateFile != "" { filename := step.Abs(p.SSH.TemplateFile) if sshTemplate.Template, err = os.ReadFile(filename); err != nil { - return nil, nil, errors.Wrap(err, "error reading ssh template") + return nil, nil, nil, errors.Wrap(err, "error reading ssh template") } } } - return x509Template, sshTemplate, nil + var webhooks []*linkedca.Webhook + for _, pwh := range p.Webhooks { + webhooks = append(webhooks, provisionerWebhookToLinkedca(pwh)) + } + + return x509Template, sshTemplate, webhooks, nil } func provisionerPEMToLinkedca(b []byte) [][]byte { @@ -676,6 +793,17 @@ func provisionerPEMToLinkedca(b []byte) [][]byte { return roots } +func provisionerPEMToCertificates(bs [][]byte) []byte { + var roots []byte + for i, root := range bs { + if i > 0 && !bytes.HasSuffix(root, []byte{'\n'}) { + roots = append(roots, '\n') + } + roots = append(roots, root...) + } + return roots +} + // ProvisionerToCertificates converts the linkedca provisioner type to the certificates provisioner // interface. func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) { @@ -752,9 +880,13 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, Type: p.Type.String(), Name: p.Name, ForceCN: cfg.ForceCn, + TermsOfService: cfg.TermsOfService, + Website: cfg.Website, + CaaIdentities: cfg.CaaIdentities, RequireEAB: cfg.RequireEab, Challenges: challengesToCertificates(cfg.Challenges), AttestationFormats: attestationFormatsToCertificates(cfg.AttestationFormats), + AttestationRoots: provisionerPEMToCertificates(cfg.AttestationRoots), Claims: claims, Options: options, }, nil @@ -867,7 +999,7 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, error) { switch p := p.(type) { case *provisioner.JWK: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -890,9 +1022,10 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil case *provisioner.OIDC: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -917,9 +1050,10 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil case *provisioner.GCP: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -941,9 +1075,10 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil case *provisioner.AWS: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -964,9 +1099,10 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil case *provisioner.Azure: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -990,9 +1126,10 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil case *provisioner.ACME: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -1004,17 +1141,23 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Data: &linkedca.ProvisionerDetails_ACME{ ACME: &linkedca.ACMEProvisioner{ ForceCn: p.ForceCN, + TermsOfService: p.TermsOfService, + Website: p.Website, + CaaIdentities: p.CaaIdentities, + RequireEab: p.RequireEAB, Challenges: challengesToLinkedca(p.Challenges), AttestationFormats: attestationFormatsToLinkedca(p.AttestationFormats), + AttestationRoots: provisionerPEMToLinkedca(p.AttestationRoots), }, }, }, Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil case *provisioner.X5C: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -1032,9 +1175,10 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil case *provisioner.K8sSA: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -1052,6 +1196,7 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil case *provisioner.SSHPOP: return &linkedca.Provisioner{ @@ -1066,7 +1211,7 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), }, nil case *provisioner.SCEP: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -1089,9 +1234,10 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil case *provisioner.Nebula: - x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options) + x509Template, sshTemplate, webhooks, err := provisionerOptionsToLinkedca(p.Options) if err != nil { return nil, err } @@ -1109,6 +1255,7 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Claims: claimsToLinkedca(p.Claims), X509Template: x509Template, SshTemplate: sshTemplate, + Webhooks: webhooks, }, nil default: return nil, fmt.Errorf("provisioner %s not implemented", p.GetType()) diff --git a/authority/provisioners_test.go b/authority/provisioners_test.go index 56cd16b1..7901de6a 100644 --- a/authority/provisioners_test.go +++ b/authority/provisioners_test.go @@ -16,6 +16,7 @@ import ( "github.com/smallstep/certificates/db" "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" + "go.step.sm/linkedca" ) func TestGetEncryptedKey(t *testing.T) { @@ -57,9 +58,10 @@ func TestGetEncryptedKey(t *testing.T) { ek, err := tc.a.GetEncryptedKey(tc.kid) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { @@ -107,9 +109,10 @@ func TestGetProvisioners(t *testing.T) { ps, next, err := tc.a.GetProvisioners("", 0) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { @@ -251,3 +254,133 @@ func TestAuthority_LoadProvisionerByCertificate(t *testing.T) { }) } } + +func TestProvisionerWebhookToLinkedca(t *testing.T) { + type test struct { + lwh *linkedca.Webhook + pwh *provisioner.Webhook + } + tests := map[string]test{ + "empty": test{ + lwh: &linkedca.Webhook{}, + pwh: &provisioner.Webhook{Kind: "NO_KIND", CertType: "ALL"}, + }, + "enriching ssh basic auth": test{ + lwh: &linkedca.Webhook{ + Id: "abc123", + Name: "people", + Url: "https://localhost", + Kind: linkedca.Webhook_ENRICHING, + Secret: "secret", + Auth: &linkedca.Webhook_BasicAuth{ + BasicAuth: &linkedca.BasicAuth{ + Username: "user", + Password: "pass", + }, + }, + DisableTlsClientAuth: true, + CertType: linkedca.Webhook_SSH, + }, + pwh: &provisioner.Webhook{ + ID: "abc123", + Name: "people", + URL: "https://localhost", + Kind: "ENRICHING", + Secret: "secret", + BasicAuth: struct { + Username string + Password string + }{ + Username: "user", + Password: "pass", + }, + DisableTLSClientAuth: true, + CertType: "SSH", + }, + }, + "authorizing x509 bearer auth": test{ + lwh: &linkedca.Webhook{ + Id: "abc123", + Name: "people", + Url: "https://localhost", + Kind: linkedca.Webhook_AUTHORIZING, + Secret: "secret", + Auth: &linkedca.Webhook_BearerToken{ + BearerToken: &linkedca.BearerToken{ + BearerToken: "tkn", + }, + }, + CertType: linkedca.Webhook_X509, + }, + pwh: &provisioner.Webhook{ + ID: "abc123", + Name: "people", + URL: "https://localhost", + Kind: "AUTHORIZING", + Secret: "secret", + BearerToken: "tkn", + CertType: "X509", + }, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + gotLWH := provisionerWebhookToLinkedca(test.pwh) + assert.Equals(t, test.lwh, gotLWH) + + gotPWH := webhookToCertificates(test.lwh) + assert.Equals(t, test.pwh, gotPWH) + }) + } +} + +func Test_wrapRAProvisioner(t *testing.T) { + type args struct { + p provisioner.Interface + raInfo *provisioner.RAInfo + } + tests := []struct { + name string + args args + want *wrappedProvisioner + }{ + {"ok", args{&provisioner.JWK{Name: "jwt"}, &provisioner.RAInfo{ProvisionerName: "ra"}}, &wrappedProvisioner{ + Interface: &provisioner.JWK{Name: "jwt"}, + raInfo: &provisioner.RAInfo{ProvisionerName: "ra"}, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := wrapRAProvisioner(tt.args.p, tt.args.raInfo); !reflect.DeepEqual(got, tt.want) { + t.Errorf("wrapRAProvisioner() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_isRAProvisioner(t *testing.T) { + type args struct { + p provisioner.Interface + } + tests := []struct { + name string + args args + want bool + }{ + {"true", args{&wrappedProvisioner{ + Interface: &provisioner.JWK{Name: "jwt"}, + raInfo: &provisioner.RAInfo{ProvisionerName: "ra"}, + }}, true}, + {"nil ra", args{&wrappedProvisioner{ + Interface: &provisioner.JWK{Name: "jwt"}, + }}, false}, + {"not ra", args{&provisioner.JWK{Name: "jwt"}}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isRAProvisioner(tt.args.p); got != tt.want { + t.Errorf("isRAProvisioner() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/authority/root_test.go b/authority/root_test.go index a1b08fac..e570b0be 100644 --- a/authority/root_test.go +++ b/authority/root_test.go @@ -32,8 +32,8 @@ func TestRoot(t *testing.T) { crt, err := a.Root(tc.sum) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCoder interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) } diff --git a/authority/ssh.go b/authority/ssh.go index d8d5375c..7d990904 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "encoding/binary" "errors" - "fmt" "net/http" "strings" "time" @@ -20,8 +19,8 @@ import ( "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" - policy "github.com/smallstep/certificates/policy" "github.com/smallstep/certificates/templates" + "github.com/smallstep/certificates/webhook" ) const ( @@ -140,6 +139,7 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user, hostname string) (* return a.config.SSH.Bastion, nil } } + //nolint:nilnil // legacy return nil, nil } return nil, errs.NotFound("authority.GetSSHBastion; ssh is not configured") @@ -162,6 +162,7 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi opts.Backdate = a.config.AuthorityConfig.Backdate.Duration var prov provisioner.Interface + var webhookCtl webhookController for _, op := range signOpts { switch o := op.(type) { // Capture current provisioner @@ -186,6 +187,10 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi return nil, errs.BadRequestErr(err, "error validating ssh certificate options") } + // call webhooks + case webhookController: + webhookCtl = o + default: return nil, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o) } @@ -199,10 +204,19 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi Key: key, } + // Call enriching webhooks + if err := callEnrichingWebhooksSSH(webhookCtl, cr); err != nil { + return nil, errs.ApplyOptions( + errs.ForbiddenErr(err, err.Error()), + errs.WithKeyVal("signOptions", signOpts), + ) + } + // Create certificate from template. certificate, err := sshutil.NewCertificate(cr, certOptions...) if err != nil { - if _, ok := err.(*sshutil.TemplateError); ok { + var te *sshutil.TemplateError + if errors.As(err, &te) { return nil, errs.ApplyOptions( errs.BadRequestErr(err, err.Error()), errs.WithKeyVal("signOptions", signOpts), @@ -253,21 +267,22 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi // Check if authority is allowed to sign the certificate if err := a.isAllowedToSignSSHCertificate(certTpl); err != nil { - var pe *policy.NamePolicyError - if errors.As(err, &pe) && pe.Reason == policy.NotAllowed { - return nil, &errs.Error{ - // NOTE: custom forbidden error, so that denied name is sent to client - // as well as shown in the logs. - Status: http.StatusForbidden, - Err: fmt.Errorf("authority not allowed to sign: %w", err), - Msg: fmt.Sprintf("The request was forbidden by the certificate authority: %s", err.Error()), - } + var ee *errs.Error + if errors.As(err, &ee) { + return nil, ee } return nil, errs.InternalServerErr(err, errs.WithMessage("authority.SignSSH: error creating ssh certificate"), ) } + // Send certificate to webhooks for authorization + if err := callAuthorizingWebhooksSSH(webhookCtl, certificate, certTpl); err != nil { + return nil, errs.ApplyOptions( + errs.ForbiddenErr(err, "authority.SignSSH: error signing certificate"), + ) + } + // Sign certificate. cert, err := sshutil.CreateCertificate(certTpl, signer) if err != nil { @@ -281,7 +296,7 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi } } - if err = a.storeSSHCertificate(prov, cert); err != nil && err != db.ErrNotImplemented { + if err = a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db") } @@ -351,7 +366,7 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") } - if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && err != db.ErrNotImplemented { + if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db") } @@ -434,7 +449,7 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub } } - if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && err != db.ErrNotImplemented { + if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db") } @@ -570,7 +585,7 @@ func (a *Authority) SignSSHAddUser(ctx context.Context, key ssh.PublicKey, subje } cert.Signature = sig - if err = a.storeRenewedSSHCertificate(prov, subject, cert); err != nil && err != db.ErrNotImplemented { + if err = a.storeRenewedSSHCertificate(prov, subject, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSHAddUser: error storing certificate in db") } @@ -589,7 +604,7 @@ func (a *Authority) CheckSSHHost(ctx context.Context, principal, token string) ( } exists, err := a.db.IsSSHHost(principal) if err != nil { - if err == db.ErrNotImplemented { + if errors.Is(err, db.ErrNotImplemented) { return false, errs.Wrap(http.StatusNotImplemented, err, "checkSSHHost: isSSHHost is not implemented") } @@ -637,3 +652,37 @@ func (a *Authority) getAddUserCommand(principal string) string { } return strings.ReplaceAll(cmd, "", principal) } + +func callEnrichingWebhooksSSH(webhookCtl webhookController, cr sshutil.CertificateRequest) error { + if webhookCtl == nil { + return nil + } + whEnrichReq, err := webhook.NewRequestBody( + webhook.WithSSHCertificateRequest(cr), + ) + if err != nil { + return err + } + if err := webhookCtl.Enrich(whEnrichReq); err != nil { + return err + } + + return nil +} + +func callAuthorizingWebhooksSSH(webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) error { + if webhookCtl == nil { + return nil + } + whAuthBody, err := webhook.NewRequestBody( + webhook.WithSSHCertificate(cert, certTpl), + ) + if err != nil { + return err + } + if err := webhookCtl.Authorize(whAuthBody); err != nil { + return err + } + + return nil +} diff --git a/authority/ssh_test.go b/authority/ssh_test.go index 4fd7eaa0..b24be941 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -178,6 +178,17 @@ func TestAuthority_SignSSH(t *testing.T) { }`}, }, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", []string{"user"})) assert.FatalError(t, err) + enrichTemplateData := sshutil.CreateTemplateData(sshutil.UserCert, "key-id", []string{"user"}) + enrichTemplate, err := provisioner.TemplateSSHOptions(&provisioner.Options{ + SSH: &provisioner.SSHOptions{Template: `{ + "type": "{{ .Type }}", + "keyId": "{{ .KeyID }}", + "principals": {{ toJson .Webhooks.people.role }}, + "extensions": {{ set .Extensions "login@github.com" .Insecure.User.username | toJson }}, + "criticalOptions": {{ toJson .CriticalOptions }} + }`}, + }, enrichTemplateData) + assert.FatalError(t, err) userFailTemplate, err := provisioner.TemplateSSHOptions(&provisioner.Options{ SSH: &provisioner.SSHOptions{Template: `{{ fail "an error"}}`}, }, sshutil.CreateTemplateData(sshutil.UserCert, "key-id", []string{"user"})) @@ -255,6 +266,7 @@ func TestAuthority_SignSSH(t *testing.T) { {"ok-opts-validator", fields{signer, signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsValidator("")}}, want{CertType: ssh.UserCert}, false}, {"ok-opts-modifier", fields{signer, signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, sshTestOptionsModifier("")}}, want{CertType: ssh.UserCert}, false}, {"ok-custom-template", fields{signer, signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userCustomTemplate, userOptions}}, want{CertType: ssh.UserCert, Principals: []string{"user", "admin"}}, false}, + {"ok-enrich-template", fields{signer, signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{enrichTemplate, userOptions, &mockWebhookController{templateData: enrichTemplateData, respData: map[string]any{"people": map[string]any{"role": []string{"user", "eng"}}}}}}, want{CertType: ssh.UserCert, Principals: []string{"user", "eng"}}, false}, {"ok-user-policy", fields{signer, signer, userPolicy}, args{pub, provisioner.SignSSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{userTemplateWithUser}}, want{CertType: ssh.UserCert, Principals: []string{"user"}}, false}, {"ok-host-policy", fields{signer, signer, hostPolicy}, args{pub, provisioner.SignSSHOptions{CertType: "host", Principals: []string{"foo.test.com", "bar.test.com"}}, []provisioner.SignOption{hostTemplateWithHosts}}, want{CertType: ssh.HostCert, Principals: []string{"foo.test.com", "bar.test.com"}}, false}, {"fail-opts-type", fields{signer, signer, nil}, args{pub, provisioner.SignSSHOptions{CertType: "foo"}, []provisioner.SignOption{userTemplate}}, want{}, true}, @@ -275,6 +287,8 @@ func TestAuthority_SignSSH(t *testing.T) { {"fail-host-policy", fields{signer, signer, hostPolicy}, args{pub, provisioner.SignSSHOptions{CertType: "host", Principals: []string{"example.com"}}, []provisioner.SignOption{hostTemplateWithExampleDotCom}}, want{}, true}, {"fail-host-policy-with-user-cert", fields{signer, signer, hostPolicy}, args{pub, provisioner.SignSSHOptions{CertType: "user", Principals: []string{"user"}}, []provisioner.SignOption{userTemplateWithUser}}, want{}, true}, {"fail-host-policy-with-bad-host", fields{signer, signer, hostPolicy}, args{pub, provisioner.SignSSHOptions{CertType: "host", Principals: []string{"example.com"}}, []provisioner.SignOption{badHostTemplate}}, want{}, true}, + {"fail-enriching-webhooks", fields{signer, signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, &mockWebhookController{enrichErr: provisioner.ErrWebhookDenied}}}, want{}, true}, + {"fail-authorizing-webhooks", fields{signer, signer, nil}, args{pub, provisioner.SignSSHOptions{}, []provisioner.SignOption{userTemplate, userOptions, &mockWebhookController{authorizeErr: provisioner.ErrWebhookDenied}}}, want{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -760,8 +774,8 @@ func TestAuthority_GetSSHBastion(t *testing.T) { t.Errorf("Authority.GetSSHBastion() error = %v, wantErr %v", err, tt.wantErr) return } else if err != nil { - _, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Authority.GetSSHBastion() = %v, want %v", got, tt.want) @@ -850,9 +864,10 @@ func TestAuthority_GetSSHHosts(t *testing.T) { hosts, err := auth.GetSSHHosts(context.Background(), tc.cert) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { @@ -1077,9 +1092,10 @@ func TestAuthority_RekeySSH(t *testing.T) { cert, err := auth.RekeySSH(context.Background(), tc.cert, tc.key, tc.signOpts...) if err != nil { if assert.NotNil(t, tc.err) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tc.code) + } assert.HasPrefix(t, err.Error(), tc.err.Error()) } } else { diff --git a/authority/tls.go b/authority/tls.go index 632ac238..11c61b9e 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -5,11 +5,13 @@ import ( "crypto" "crypto/tls" "crypto/x509" + "crypto/x509/pkix" "encoding/asn1" "encoding/base64" "encoding/json" "encoding/pem" "fmt" + "math/big" "net" "net/http" "strings" @@ -28,16 +30,33 @@ import ( casapi "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" - "github.com/smallstep/certificates/policy" + "github.com/smallstep/certificates/webhook" + "github.com/smallstep/nosql/database" ) +type tokenKey struct{} + +// NewTokenContext adds the given token to the context. +func NewTokenContext(ctx context.Context, token string) context.Context { + return context.WithValue(ctx, tokenKey{}, token) +} + +// TokenFromContext returns the token from the given context. +func TokenFromContext(ctx context.Context) (token string, ok bool) { + token, ok = ctx.Value(tokenKey{}).(string) + return +} + // GetTLSOptions returns the tls options configured. func (a *Authority) GetTLSOptions() *config.TLSOptions { return a.config.TLS } -var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35} -var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} +var ( + oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35} + oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} + oidExtensionIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28} +) func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { return func(crt *x509.Certificate, opts provisioner.SignOptions) error { @@ -94,7 +113,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign var prov provisioner.Interface var pInfo *casapi.ProvisionerInfo - var attData provisioner.AttestationData + var attData *provisioner.AttestationData + var webhookCtl webhookController for _, op := range extraOpts { switch k := op.(type) { // Capture current provisioner @@ -132,17 +152,29 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Extra information from ACME attestations. case provisioner.AttestationData: - attData = k - // TODO(mariano,areed): remove me once attData is used. - _ = attData + attData = &k + + // Capture the provisioner's webhook controller + case webhookController: + webhookCtl = k + default: return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...) } } + if err := callEnrichingWebhooksX509(webhookCtl, attData, csr); err != nil { + return nil, errs.ApplyOptions( + errs.ForbiddenErr(err, err.Error()), + errs.WithKeyVal("csr", csr), + errs.WithKeyVal("signOptions", signOpts), + ) + } + cert, err := x509util.NewCertificate(csr, certOptions...) if err != nil { - if _, ok := err.(*x509util.TemplateError); ok { + var te *x509util.TemplateError + if errors.As(err, &te) { return nil, errs.ApplyOptions( errs.BadRequestErr(err, err.Error()), errs.WithKeyVal("csr", csr), @@ -212,15 +244,9 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Check if authority is allowed to sign the certificate if err := a.isAllowedToSignX509Certificate(leaf); err != nil { - var pe *policy.NamePolicyError - if errors.As(err, &pe) && pe.Reason == policy.NotAllowed { - return nil, errs.ApplyOptions(&errs.Error{ - // NOTE: custom forbidden error, so that denied name is sent to client - // as well as shown in the logs. - Status: http.StatusForbidden, - Err: fmt.Errorf("authority not allowed to sign: %w", err), - Msg: fmt.Sprintf("The request was forbidden by the certificate authority: %s", err.Error()), - }, opts...) + var ee *errs.Error + if errors.As(err, &ee) { + return nil, errs.ApplyOptions(ee, opts...) } return nil, errs.InternalServerErr(err, errs.WithKeyVal("csr", csr), @@ -229,6 +255,14 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign ) } + // Send certificate to webhooks for authorization + if err := callAuthorizingWebhooksX509(webhookCtl, cert, leaf, attData); err != nil { + return nil, errs.ApplyOptions( + errs.ForbiddenErr(err, "error creating certificate"), + opts..., + ) + } + // Sign certificate lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate)) resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ @@ -243,8 +277,13 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign } fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) + + // Wrap provisioner with extra information. + prov = wrapProvisioner(prov, attData) + + // Store certificate in the db. if err = a.storeCertificate(prov, fullchain); err != nil { - if err != db.ErrNotImplemented { + if !errors.Is(err, db.ErrNotImplemented) { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error storing certificate in db", opts...) } @@ -256,6 +295,9 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // isAllowedToSignX509Certificate checks if the Authority is allowed // to sign the X.509 certificate. func (a *Authority) isAllowedToSignX509Certificate(cert *x509.Certificate) error { + if err := a.constraintsEngine.ValidateCertificate(cert); err != nil { + return err + } return a.policyEngine.IsX509CertificateAllowed(cert) } @@ -265,28 +307,44 @@ func (a *Authority) AreSANsAllowed(ctx context.Context, sans []string) error { return a.policyEngine.AreSANsAllowed(sans) } -// Renew creates a new Certificate identical to the old certificate, except -// with a validity window that begins 'now'. +// Renew creates a new Certificate identical to the old certificate, except with +// a validity window that begins 'now'. func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error) { - return a.Rekey(oldCert, nil) + return a.RenewContext(context.Background(), oldCert, nil) } -// Rekey is used for rekeying and renewing based on the public key. -// If the public key is 'nil' then it's assumed that the cert should be renewed -// using the existing public key. If the public key is not 'nil' then it's -// assumed that the cert should be rekeyed. +// Rekey is used for rekeying and renewing based on the public key. If the +// public key is 'nil' then it's assumed that the cert should be renewed using +// the existing public key. If the public key is not 'nil' then it's assumed +// that the cert should be rekeyed. +// // For both Rekey and Renew all other attributes of the new certificate should // match the old certificate. The exceptions are 'AuthorityKeyId' (which may // have changed), 'SubjectKeyId' (different in case of rekey), and // 'NotBefore/NotAfter' (the validity duration of the new certificate should be // equal to the old one, but starting 'now'). func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { + return a.RenewContext(context.Background(), oldCert, pk) +} + +// RenewContext creates a new certificate identical to the old one, but it can +// optionally replace the public key with the given one. When running on RA +// mode, it can only renew a certificate using a renew token instead. +// +// For both rekey and renew operations, all other attributes of the new +// certificate should match the old certificate. The exceptions are +// 'AuthorityKeyId' (which may have changed), 'SubjectKeyId' (different in case +// of rekey), and 'NotBefore/NotAfter' (the validity duration of the new +// certificate should be equal to the old one, but starting 'now'). +func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { isRekey := (pk != nil) - opts := []interface{}{errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String())} + opts := []errs.Option{ + errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), + } // Check step provisioner extensions - if err := a.authorizeRenew(oldCert); err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...) + if err := a.authorizeRenew(ctx, oldCert); err != nil { + return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) } // Durations @@ -297,7 +355,7 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5 // Create new certificate from previous values. // Issuer, NotBefore, NotAfter and SubjectKeyId will be set by the CAS. newCert := &x509.Certificate{ - Subject: oldCert.Subject, + RawSubject: oldCert.RawSubject, KeyUsage: oldCert.KeyUsage, UnhandledCriticalExtensions: oldCert.UnhandledCriticalExtensions, ExtKeyUsage: oldCert.ExtKeyUsage, @@ -351,19 +409,40 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5 newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext) } + // Check if the certificate is allowed to be renewed, name constraints might + // change over time. + // + // TODO(hslatman,maraino): consider adding policies too and consider if + // RenewSSH should check policies. + if err := a.constraintsEngine.ValidateCertificate(newCert); err != nil { + var ee *errs.Error + if errors.As(err, &ee) { + return nil, errs.StatusCodeError(ee.StatusCode(), err, opts...) + } + return nil, errs.InternalServerErr(err, + errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), + errs.WithMessage("error renewing certificate"), + ) + } + + // The token can optionally be in the context. If the CA is running in RA + // mode, this can be used to renew a certificate. + token, _ := TokenFromContext(ctx) + resp, err := a.x509CAService.RenewCertificate(&casapi.RenewCertificateRequest{ Template: newCert, Lifetime: lifetime, Backdate: backdate, + Token: token, }) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...) + return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) } fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) if err = a.storeRenewedCertificate(oldCert, fullchain); err != nil { - if err != db.ErrNotImplemented { - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey; error storing certificate in db", opts...) + if !errors.Is(err, db.ErrNotImplemented) { + return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) } } @@ -473,16 +552,23 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error RevokedAt: time.Now().UTC(), } - var ( - p provisioner.Interface - err error - ) + // For X509 CRLs attempt to get the expiration date of the certificate. + if provisioner.MethodFromContext(ctx) == provisioner.RevokeMethod { + if revokeOpts.Crt == nil { + cert, err := a.db.GetCertificate(revokeOpts.Serial) + if err == nil { + rci.ExpiresAt = cert.NotAfter + } + } else { + rci.ExpiresAt = revokeOpts.Crt.NotAfter + } + } + // If not mTLS nor ACME, then get the TokenID of the token. if !(revokeOpts.MTLS || revokeOpts.ACME) { token, err := jose.ParseSigned(revokeOpts.OTT) if err != nil { - return errs.Wrap(http.StatusUnauthorized, err, - "authority.Revoke; error parsing token", opts...) + return errs.Wrap(http.StatusUnauthorized, err, "authority.Revoke; error parsing token", opts...) } // Get claims w/out verification. @@ -492,28 +578,43 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error } // This method will also validate the audiences for JWK provisioners. - p, err = a.LoadProvisionerByToken(token, &claims.Claims) + p, err := a.LoadProvisionerByToken(token, &claims.Claims) if err != nil { return err } rci.ProvisionerID = p.GetID() rci.TokenID, err = p.GetTokenID(revokeOpts.OTT) if err != nil && !errors.Is(err, provisioner.ErrAllowTokenReuse) { - return errs.Wrap(http.StatusInternalServerError, err, - "authority.Revoke; could not get ID for token") + return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke; could not get ID for token") } opts = append(opts, errs.WithKeyVal("provisionerID", rci.ProvisionerID), errs.WithKeyVal("tokenID", rci.TokenID), ) - } else if p, err = a.LoadProvisionerByCertificate(revokeOpts.Crt); err == nil { + } else if p, err := a.LoadProvisionerByCertificate(revokeOpts.Crt); err == nil { // Load the Certificate provisioner if one exists. rci.ProvisionerID = p.GetID() opts = append(opts, errs.WithKeyVal("provisionerID", rci.ProvisionerID)) } + failRevoke := func(err error) error { + switch { + case errors.Is(err, db.ErrNotImplemented): + return errs.NotImplemented("authority.Revoke; no persistence layer configured", opts...) + case errors.Is(err, db.ErrAlreadyExists): + return errs.ApplyOptions( + errs.BadRequest("certificate with serial number '%s' is already revoked", rci.Serial), + opts..., + ) + default: + return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...) + } + } + if provisioner.MethodFromContext(ctx) == provisioner.SSHRevokeMethod { - err = a.revokeSSH(nil, rci) + if err := a.revokeSSH(nil, rci); err != nil { + return failRevoke(err) + } } else { // Revoke an X.509 certificate using CAS. If the certificate is not // provided we will try to read it from the db. If the read fails we @@ -528,7 +629,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error // CAS operation, note that SoftCAS (default) is a noop. // The revoke happens when this is stored in the db. - _, err = a.x509CAService.RevokeCertificate(&casapi.RevokeCertificateRequest{ + _, err := a.x509CAService.RevokeCertificate(&casapi.RevokeCertificateRequest{ Certificate: revokedCert, SerialNumber: rci.Serial, Reason: rci.Reason, @@ -540,21 +641,20 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error } // Save as revoked in the Db. - err = a.revoke(revokedCert, rci) - } - switch err { - case nil: - return nil - case db.ErrNotImplemented: - return errs.NotImplemented("authority.Revoke; no persistence layer configured", opts...) - case db.ErrAlreadyExists: - return errs.ApplyOptions( - errs.BadRequest("certificate with serial number '%s' is already revoked", rci.Serial), - opts..., - ) - default: - return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...) + if err := a.revoke(revokedCert, rci); err != nil { + return failRevoke(err) + } + + // Generate a new CRL so CRL requesters will always get an up-to-date + // CRL whenever they request it. + if a.config.CRL.IsEnabled() && a.config.CRL.GenerateOnRevoke { + if err := a.GenerateCertificateRevocationList(); err != nil { + return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...) + } + } } + + return nil } func (a *Authority) revoke(crt *x509.Certificate, rci *db.RevokedCertificateInfo) error { @@ -575,6 +675,137 @@ func (a *Authority) revokeSSH(crt *ssh.Certificate, rci *db.RevokedCertificateIn return a.db.RevokeSSH(rci) } +// GetCertificateRevocationList will return the currently generated CRL from the DB, or a not implemented +// error if the underlying AuthDB does not support CRLs +func (a *Authority) GetCertificateRevocationList() ([]byte, error) { + if !a.config.CRL.IsEnabled() { + return nil, errs.Wrap(http.StatusNotFound, errors.Errorf("Certificate Revocation Lists are not enabled"), "authority.GetCertificateRevocationList") + } + + crlDB, ok := a.db.(db.CertificateRevocationListDB) + if !ok { + return nil, errs.Wrap(http.StatusNotImplemented, errors.Errorf("Database does not support Certificate Revocation Lists"), "authority.GetCertificateRevocationList") + } + + crlInfo, err := crlDB.GetCRL() + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetCertificateRevocationList") + } + + return crlInfo.DER, nil +} + +// GenerateCertificateRevocationList generates a DER representation of a signed CRL and stores it in the +// database. Returns nil if CRL generation has been disabled in the config +func (a *Authority) GenerateCertificateRevocationList() error { + if !a.config.CRL.IsEnabled() { + return nil + } + + crlDB, ok := a.db.(db.CertificateRevocationListDB) + if !ok { + return errors.Errorf("Database does not support CRL generation") + } + + // some CAS may not implement the CRLGenerator interface, so check before we proceed + caCRLGenerator, ok := a.x509CAService.(casapi.CertificateAuthorityCRLGenerator) + if !ok { + return errors.Errorf("CA does not support CRL Generation") + } + + // use a mutex to ensure only one CRL is generated at a time to avoid + // concurrency issues + a.crlMutex.Lock() + defer a.crlMutex.Unlock() + + crlInfo, err := crlDB.GetCRL() + if err != nil && !database.IsErrNotFound(err) { + return errors.Wrap(err, "could not retrieve CRL from database") + } + + now := time.Now().Truncate(time.Second).UTC() + revokedList, err := crlDB.GetRevokedCertificates() + if err != nil { + return errors.Wrap(err, "could not retrieve revoked certificates list from database") + } + + // Number is a monotonically increasing integer (essentially the CRL version + // number) that we need to keep track of and increase every time we generate + // a new CRL + var bn big.Int + if crlInfo != nil { + bn.SetInt64(crlInfo.Number + 1) + } + + // Convert our database db.RevokedCertificateInfo types into the pkix + // representation ready for the CAS to sign it + var revokedCertificates []pkix.RevokedCertificate + skipExpiredTime := now.Add(-config.DefaultCRLExpiredDuration) + for _, revokedCert := range *revokedList { + // skip expired certificates + if !revokedCert.ExpiresAt.IsZero() && revokedCert.ExpiresAt.Before(skipExpiredTime) { + continue + } + + var sn big.Int + sn.SetString(revokedCert.Serial, 10) + revokedCertificates = append(revokedCertificates, pkix.RevokedCertificate{ + SerialNumber: &sn, + RevocationTime: revokedCert.RevokedAt, + Extensions: nil, + }) + } + + var updateDuration time.Duration + if a.config.CRL.CacheDuration != nil { + updateDuration = a.config.CRL.CacheDuration.Duration + } else if crlInfo != nil { + updateDuration = crlInfo.Duration + } + + // Create a RevocationList representation ready for the CAS to sign + // TODO: allow SignatureAlgorithm to be specified? + revocationList := x509.RevocationList{ + SignatureAlgorithm: 0, + RevokedCertificates: revokedCertificates, + Number: &bn, + ThisUpdate: now, + NextUpdate: now.Add(updateDuration), + } + + // Add distribution point. + // + // Note that this is currently using the port 443 by default. + fullName := a.config.Audience("/1.0/crl")[0] + if b, err := marshalDistributionPoint(fullName, false); err == nil { + revocationList.ExtraExtensions = []pkix.Extension{ + {Id: oidExtensionIssuingDistributionPoint, Value: b}, + } + } + + certificateRevocationList, err := caCRLGenerator.CreateCRL(&casapi.CreateCRLRequest{RevocationList: &revocationList}) + if err != nil { + return errors.Wrap(err, "could not create CRL") + } + + // Create a new db.CertificateRevocationListInfo, which stores the new Number we just generated, the + // expiry time, duration, and the DER-encoded CRL + newCRLInfo := db.CertificateRevocationListInfo{ + Number: bn.Int64(), + ExpiresAt: revocationList.NextUpdate, + DER: certificateRevocationList.CRL, + Duration: updateDuration, + } + + // Store the CRL in the database ready for retrieval by api endpoints + err = crlDB.StoreCRL(&newCRLInfo) + if err != nil { + return errors.Wrap(err, "could not store CRL in database") + } + + return nil +} + // GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server. func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { fatal := func(err error) (*tls.Certificate, error) { @@ -620,6 +851,18 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { certTpl.NotBefore = now.Add(-1 * time.Minute) certTpl.NotAfter = now.Add(24 * time.Hour) + // Policy and constraints require this fields to be set. At this moment they + // are only present in the extra extension. + certTpl.DNSNames = cr.DNSNames + certTpl.IPAddresses = cr.IPAddresses + certTpl.EmailAddresses = cr.EmailAddresses + certTpl.URIs = cr.URIs + + // Fail if name constraints do not allow the server names. + if err := a.constraintsEngine.ValidateCertificate(certTpl); err != nil { + return fatal(err) + } + resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ Template: certTpl, CSR: cr, @@ -656,6 +899,33 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { return &tlsCrt, nil } +// RFC 5280, 5.2.5 +type distributionPoint struct { + DistributionPoint distributionPointName `asn1:"optional,tag:0"` + OnlyContainsUserCerts bool `asn1:"optional,tag:1"` + OnlyContainsCACerts bool `asn1:"optional,tag:2"` + OnlySomeReasons asn1.BitString `asn1:"optional,tag:3"` + IndirectCRL bool `asn1:"optional,tag:4"` + OnlyContainsAttributeCerts bool `asn1:"optional,tag:5"` +} + +type distributionPointName struct { + FullName []asn1.RawValue `asn1:"optional,tag:0"` + RelativeName pkix.RDNSequence `asn1:"optional,tag:1"` +} + +func marshalDistributionPoint(fullName string, isCA bool) ([]byte, error) { + return asn1.Marshal(distributionPoint{ + DistributionPoint: distributionPointName{ + FullName: []asn1.RawValue{ + {Class: 2, Tag: 6, Bytes: []byte(fullName)}, + }, + }, + OnlyContainsUserCerts: !isCA, + OnlyContainsCACerts: isCA, + }) +} + // templatingError tries to extract more information about the cause of // an error related to (most probably) malformed template data and adds // this to the error message. @@ -667,10 +937,58 @@ func templatingError(err error) error { ) if errors.As(err, &syntaxError) { // offset is arguably not super clear to the user, but it's the best we can do here - cause = fmt.Errorf("%s at offset %d", cause.Error(), syntaxError.Offset) + cause = fmt.Errorf("%w at offset %d", cause, syntaxError.Offset) } else if errors.As(err, &typeError) { // slightly rewriting the default error message to include the offset cause = fmt.Errorf("cannot unmarshal %s at offset %d into Go value of type %s", typeError.Value, typeError.Offset, typeError.Type) } return errors.Wrap(cause, "error applying certificate template") } + +func callEnrichingWebhooksX509(webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) error { + if webhookCtl == nil { + return nil + } + var attested *webhook.AttestationData + if attData != nil { + attested = &webhook.AttestationData{ + PermanentIdentifier: attData.PermanentIdentifier, + } + } + whEnrichReq, err := webhook.NewRequestBody( + webhook.WithX509CertificateRequest(csr), + webhook.WithAttestationData(attested), + ) + if err != nil { + return err + } + if err := webhookCtl.Enrich(whEnrichReq); err != nil { + return err + } + + return nil +} + +func callAuthorizingWebhooksX509(webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) error { + if webhookCtl == nil { + return nil + } + var attested *webhook.AttestationData + if attData != nil { + attested = &webhook.AttestationData{ + PermanentIdentifier: attData.PermanentIdentifier, + } + } + whAuthBody, err := webhook.NewRequestBody( + webhook.WithX509Certificate(cert, leaf), + webhook.WithAttestationData(attested), + ) + if err != nil { + return err + } + if err := webhookCtl.Authorize(whAuthBody); err != nil { + return err + } + + return nil +} diff --git a/authority/tls_test.go b/authority/tls_test.go index a8521b51..5d63b3dd 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -6,7 +6,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" - "crypto/sha1" // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + "crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 "crypto/x509" "crypto/x509/pkix" "encoding/asn1" @@ -18,20 +18,21 @@ import ( "testing" "time" - "gopkg.in/square/go-jose.v2/jwt" - "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/minica" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" "github.com/smallstep/assert" "github.com/smallstep/certificates/api/render" + "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/policy" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/cas/softcas" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" + "github.com/smallstep/nosql/database" ) var ( @@ -58,6 +59,15 @@ func (m *certificateDurationEnforcer) Enforce(cert *x509.Certificate) error { return nil } +type certificateChainDB struct { + db.MockAuthDB + MStoreCertificateChain func(provisioner.Interface, ...*x509.Certificate) error +} + +func (d *certificateChainDB) StoreCertificateChain(p provisioner.Interface, certs ...*x509.Certificate) error { + return d.MStoreCertificateChain(p, certs...) +} + func getDefaultIssuer(a *Authority) *x509.Certificate { return a.x509CAService.(*softcas.SoftCAS).CertificateChain[len(a.x509CAService.(*softcas.SoftCAS).CertificateChain)-1] } @@ -131,6 +141,13 @@ func generateIntermidiateCertificate(t *testing.T, issuer *x509.Certificate, sig return cert, priv } +func withSubject(sub pkix.Name) provisioner.CertificateModifierFunc { + return func(crt *x509.Certificate, _ provisioner.SignOptions) error { + crt.Subject = sub + return nil + } +} + func withProvisionerOID(name, kid string) provisioner.CertificateModifierFunc { return func(crt *x509.Certificate, _ provisioner.SignOptions) error { b, err := asn1.Marshal(stepProvisionerASN1{ @@ -199,7 +216,7 @@ func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { if _, err = asn1.Unmarshal(b, &info); err != nil { return nil, fmt.Errorf("error unmarshaling public key: %w", err) } - // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 hash := sha1.Sum(info.SubjectPublicKey.Bytes) return hash[:], nil } @@ -542,10 +559,40 @@ ZYtQ9Ot36qc= notBefore: signOpts.NotBefore.Time().Truncate(time.Second), notAfter: signOpts.NotAfter.Time().Truncate(time.Second), extensionsCount: 6, - err: errors.New("authority not allowed to sign"), + err: errors.New("dns name \"test.smallstep.com\" not allowed"), code: http.StatusForbidden, } }, + "fail enriching webhooks": func(t *testing.T) *signTest { + csr := getCSR(t, priv) + csr.Raw = []byte("foo") + return &signTest{ + auth: a, + csr: csr, + extensionsCount: 7, + extraOpts: append(extraOpts, &mockWebhookController{ + enrichErr: provisioner.ErrWebhookDenied, + }), + signOpts: signOpts, + err: provisioner.ErrWebhookDenied, + code: http.StatusForbidden, + } + }, + "fail authorizing webhooks": func(t *testing.T) *signTest { + csr := getCSR(t, priv) + csr.Raw = []byte("foo") + return &signTest{ + auth: a, + csr: csr, + extensionsCount: 7, + extraOpts: append(extraOpts, &mockWebhookController{ + authorizeErr: provisioner.ErrWebhookDenied, + }), + signOpts: signOpts, + err: provisioner.ErrWebhookDenied, + code: http.StatusForbidden, + } + }, "ok": func(t *testing.T) *signTest { csr := getCSR(t, priv) _a := testAuthority(t) @@ -577,7 +624,7 @@ ZYtQ9Ot36qc= {Id: stepOIDProvisioner, Value: []byte("foo")}, {Id: []int{1, 1, 1}, Value: []byte("bar")}})) now := time.Now().UTC() - // nolint:gocritic + //nolint:gocritic enforcedExtraOptions := append(extraOpts, &certificateDurationEnforcer{ NotBefore: now, NotAfter: now.Add(365 * 24 * time.Hour), @@ -633,6 +680,48 @@ ZYtQ9Ot36qc= extensionsCount: 6, } }, + "ok with enriching webhook": func(t *testing.T) *signTest { + csr := getCSR(t, priv) + testAuthority := testAuthority(t) + testAuthority.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template + p, ok := testAuthority.provisioners.Load("step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + if !ok { + t.Fatal("provisioner not found") + } + p.(*provisioner.JWK).Options = &provisioner.Options{ + X509: &provisioner.X509Options{Template: `{ + "subject": {"commonName": {{ toJson .Webhooks.people.role }} }, + "dnsNames": {{ toJson .Insecure.CR.DNSNames }}, + "keyUsage": ["digitalSignature"], + "extKeyUsage": ["serverAuth","clientAuth"] + }`}, + } + testExtraOpts, err := testAuthority.Authorize(ctx, token) + assert.FatalError(t, err) + testAuthority.db = &db.MockAuthDB{ + MStoreCertificate: func(crt *x509.Certificate) error { + assert.Equals(t, crt.Subject.CommonName, "smallstep test") + return nil + }, + } + for i, o := range testExtraOpts { + if wc, ok := o.(*provisioner.WebhookController); ok { + testExtraOpts[i] = &mockWebhookController{ + templateData: wc.TemplateData, + respData: map[string]any{"people": map[string]any{"role": "smallstep test"}}, + } + } + } + return &signTest{ + auth: testAuthority, + csr: csr, + extraOpts: testExtraOpts, + signOpts: signOpts, + notBefore: signOpts.NotBefore.Time().Truncate(time.Second), + notAfter: signOpts.NotAfter.Time().Truncate(time.Second), + extensionsCount: 6, + } + }, "ok/csr with no template critical SAN extension": func(t *testing.T) *signTest { csr := getCSR(t, priv, func(csr *x509.CertificateRequest) { csr.Subject = pkix.Name{} @@ -694,7 +783,6 @@ ZYtQ9Ot36qc= aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - fmt.Println(crt.Subject) assert.Equals(t, crt.Subject.CommonName, "smallstep test") return nil }, @@ -720,6 +808,38 @@ ZYtQ9Ot36qc= extensionsCount: 6, } }, + "ok with attestation data": func(t *testing.T) *signTest { + csr := getCSR(t, priv) + aa := testAuthority(t) + aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template + aa.db = &certificateChainDB{ + MStoreCertificateChain: func(prov provisioner.Interface, certs ...*x509.Certificate) error { + p, ok := prov.(attProvisioner) + if assert.True(t, ok) { + assert.Equals(t, &provisioner.AttestationData{ + PermanentIdentifier: "1234567890", + }, p.AttestationData()) + } + if assert.Len(t, 2, certs) { + assert.Equals(t, certs[0].Subject.CommonName, "smallstep test") + assert.Equals(t, certs[1].Subject.CommonName, "smallstep Intermediate CA") + } + return nil + }, + } + + return &signTest{ + auth: aa, + csr: csr, + extraOpts: append(extraOpts, provisioner.AttestationData{ + PermanentIdentifier: "1234567890", + }), + signOpts: signOpts, + notBefore: signOpts.NotBefore.Time().Truncate(time.Second), + notAfter: signOpts.NotAfter.Time().Truncate(time.Second), + extensionsCount: 6, + } + }, } for name, genTestCase := range tests { @@ -730,13 +850,13 @@ ZYtQ9Ot36qc= if err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) - ctxErr, ok := err.(*errs.Error) - assert.Fatal(t, ok, "error is not of type *errs.Error") + var ctxErr *errs.Error + assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") assert.Equals(t, ctxErr.Details["csr"], tc.csr) assert.Equals(t, ctxErr.Details["signOptions"], tc.signOpts) } @@ -841,6 +961,18 @@ func TestAuthority_Renew(t *testing.T) { withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID), withSigner(issuer, signer)) + certExtraNames := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"}, + withSubject(pkix.Name{ + CommonName: "renew", + ExtraNames: []pkix.AttributeTypeAndValue{ + {Type: asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}, Value: "dc"}, + }, + }), + withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()), + withDefaultASN1DN(a.config.AuthorityConfig.Template), + withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID), + withSigner(issuer, signer)) + certNoRenew := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"}, withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()), withDefaultASN1DN(a.config.AuthorityConfig.Template), @@ -860,14 +992,14 @@ func TestAuthority_Renew(t *testing.T) { return &renewTest{ auth: _a, cert: cert, - err: errors.New("authority.Rekey: error creating certificate"), + err: errors.New("error creating certificate"), code: http.StatusInternalServerError, }, nil }, "fail/unauthorized": func() (*renewTest, error) { return &renewTest{ cert: certNoRenew, - err: errors.New("authority.Rekey: authority.authorizeRenew: renew is disabled for provisioner 'dev'"), + err: errors.New("authority.authorizeRenew: renew is disabled for provisioner 'dev'"), code: http.StatusUnauthorized, }, nil }, @@ -880,7 +1012,7 @@ func TestAuthority_Renew(t *testing.T) { return &renewTest{ auth: aa, cert: cert, - err: errors.New("authority.Rekey: authority.authorizeRenew: not authorized"), + err: errors.New("authority.authorizeRenew: not authorized"), code: http.StatusUnauthorized, }, nil }, @@ -890,6 +1022,12 @@ func TestAuthority_Renew(t *testing.T) { cert: cert, }, nil }, + "ok/WithExtraNames": func() (*renewTest, error) { + return &renewTest{ + auth: a, + cert: certExtraNames, + }, nil + }, "ok/success-new-intermediate": func() (*renewTest, error) { rootCert, rootSigner := generateRootCertificate(t) intCert, intSigner := generateIntermidiateCertificate(t, rootCert, rootSigner) @@ -929,13 +1067,13 @@ func TestAuthority_Renew(t *testing.T) { if err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) - ctxErr, ok := err.(*errs.Error) - assert.Fatal(t, ok, "error is not of type *errs.Error") + var ctxErr *errs.Error + assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) } } else { @@ -952,15 +1090,14 @@ func TestAuthority_Renew(t *testing.T) { assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) tmplt := a.config.AuthorityConfig.Template - assert.Equals(t, leaf.Subject.String(), - pkix.Name{ - Country: []string{tmplt.Country}, - Organization: []string{tmplt.Organization}, - Locality: []string{tmplt.Locality}, - StreetAddress: []string{tmplt.StreetAddress}, - Province: []string{tmplt.Province}, - CommonName: tmplt.CommonName, - }.String()) + assert.Equals(t, leaf.RawSubject, tc.cert.RawSubject) + assert.Equals(t, leaf.Subject.Country, []string{tmplt.Country}) + assert.Equals(t, leaf.Subject.Organization, []string{tmplt.Organization}) + assert.Equals(t, leaf.Subject.Locality, []string{tmplt.Locality}) + assert.Equals(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress}) + assert.Equals(t, leaf.Subject.Province, []string{tmplt.Province}) + assert.Equals(t, leaf.Subject.CommonName, tmplt.CommonName) + assert.Equals(t, leaf.Issuer, intermediate.Subject) assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) @@ -1084,14 +1221,14 @@ func TestAuthority_Rekey(t *testing.T) { return &renewTest{ auth: _a, cert: cert, - err: errors.New("authority.Rekey: error creating certificate"), + err: errors.New("error creating certificate"), code: http.StatusInternalServerError, }, nil }, "fail/unauthorized": func() (*renewTest, error) { return &renewTest{ cert: certNoRenew, - err: errors.New("authority.Rekey: authority.authorizeRenew: renew is disabled for provisioner 'dev'"), + err: errors.New("authority.authorizeRenew: renew is disabled for provisioner 'dev'"), code: http.StatusUnauthorized, }, nil }, @@ -1136,13 +1273,13 @@ func TestAuthority_Rekey(t *testing.T) { if err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) - ctxErr, ok := err.(*errs.Error) - assert.Fatal(t, ok, "error is not of type *errs.Error") + var ctxErr *errs.Error + assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) } } else { @@ -1328,15 +1465,15 @@ func TestAuthority_Revoke(t *testing.T) { } }, "fail/nil-db": func() test { - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ @@ -1363,20 +1500,20 @@ func TestAuthority_Revoke(t *testing.T) { return true, nil }, MGetCertificate: func(sn string) (*x509.Certificate, error) { - return nil, nil + return nil, errors.New("not found") }, Err: errors.New("force"), })) - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ @@ -1403,20 +1540,20 @@ func TestAuthority_Revoke(t *testing.T) { return true, nil }, MGetCertificate: func(sn string) (*x509.Certificate, error) { - return nil, nil + return nil, errors.New("not found") }, Err: db.ErrAlreadyExists, })) - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ @@ -1447,15 +1584,15 @@ func TestAuthority_Revoke(t *testing.T) { }, })) - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ auth: _a, @@ -1539,15 +1676,15 @@ func TestAuthority_Revoke(t *testing.T) { }, })) - cl := jwt.Claims{ + cl := jose.Claims{ Subject: "sn", Issuer: validIssuer, - NotBefore: jwt.NewNumericDate(now), - Expiry: jwt.NewNumericDate(now.Add(time.Minute)), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), Audience: validAudience, ID: "44", } - raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize() + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() assert.FatalError(t, err) return test{ auth: a, @@ -1566,13 +1703,13 @@ func TestAuthority_Revoke(t *testing.T) { t.Run(name, func(t *testing.T) { if err := tc.auth.Revoke(tc.ctx, tc.opts); err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") + var sc render.StatusCodedError + assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") assert.Equals(t, sc.StatusCode(), tc.code) assert.HasPrefix(t, err.Error(), tc.err.Error()) - ctxErr, ok := err.(*errs.Error) - assert.Fatal(t, ok, "error is not of type *errs.Error") + var ctxErr *errs.Error + assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") assert.Equals(t, ctxErr.Details["serialNumber"], tc.opts.Serial) assert.Equals(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode) assert.Equals(t, ctxErr.Details["reason"], tc.opts.Reason) @@ -1589,3 +1726,231 @@ func TestAuthority_Revoke(t *testing.T) { }) } } + +func TestAuthority_constraints(t *testing.T) { + ca, err := minica.New( + minica.WithIntermediateTemplate(`{ + "subject": {{ toJson .Subject }}, + "keyUsage": ["certSign", "crlSign"], + "basicConstraints": { + "isCA": true, + "maxPathLen": 0 + }, + "nameConstraints": { + "critical": true, + "permittedDNSDomains": ["internal.example.org"], + "excludedDNSDomains": ["internal.example.com"], + "permittedIPRanges": ["192.168.1.0/24", "192.168.2.1/32"], + "excludedIPRanges": ["192.168.3.0/24", "192.168.4.0/28"], + "permittedEmailAddresses": ["root@example.org", "example.org", ".acme.org"], + "excludedEmailAddresses": ["root@example.com", "example.com", ".acme.com"], + "permittedURIDomains": ["uuid.example.org", ".acme.org"], + "excludedURIDomains": ["uuid.example.com", ".acme.com"] + } + }`), + ) + if err != nil { + t.Fatal(err) + } + + auth, err := NewEmbedded(WithX509RootCerts(ca.Root), WithX509Signer(ca.Intermediate, ca.Signer)) + if err != nil { + t.Fatal(err) + } + signer, err := keyutil.GenerateDefaultSigner() + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + sans []string + wantErr bool + }{ + {"ok dns", []string{"internal.example.org", "host.internal.example.org"}, false}, + {"ok ip", []string{"192.168.1.10", "192.168.2.1"}, false}, + {"ok email", []string{"root@example.org", "info@example.org", "info@www.acme.org"}, false}, + {"ok uri", []string{"https://uuid.example.org/b908d973-5167-4a62-abe3-6beda358d82a", "https://uuid.acme.org/1724aae1-1bb3-44fb-83c3-9a1a18df67c8"}, false}, + {"fail permitted dns", []string{"internal.acme.org"}, true}, + {"fail excluded dns", []string{"internal.example.com"}, true}, + {"fail permitted ips", []string{"192.168.2.10"}, true}, + {"fail excluded ips", []string{"192.168.3.1"}, true}, + {"fail permitted emails", []string{"root@acme.org"}, true}, + {"fail excluded emails", []string{"root@example.com"}, true}, + {"fail permitted uris", []string{"https://acme.org/uuid/7848819c-9d0b-4e12-bbff-cd66079a3444"}, true}, + {"fail excluded uris", []string{"https://uuid.example.com/d325eda7-6356-4d60-b8f6-3d64724afeb3"}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + csr, err := x509util.CreateCertificateRequest(tt.sans[0], tt.sans, signer) + if err != nil { + t.Fatal(err) + } + cert, err := ca.SignCSR(csr) + if err != nil { + t.Fatal(err) + } + + data := x509util.CreateTemplateData(tt.sans[0], tt.sans) + templateOption, err := provisioner.TemplateOptions(nil, data) + if err != nil { + t.Fatal(err) + } + + _, err = auth.Sign(csr, provisioner.SignOptions{}, templateOption) + if (err != nil) != tt.wantErr { + t.Errorf("Authority.Sign() error = %v, wantErr %v", err, tt.wantErr) + } + + _, err = auth.Renew(cert) + if (err != nil) != tt.wantErr { + t.Errorf("Authority.Renew() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestAuthority_CRL(t *testing.T) { + reasonCode := 2 + reason := "bob was let go" + validIssuer := "step-cli" + validAudience := testAudiences.Revoke + now := time.Now().UTC() + // + jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass"))) + assert.FatalError(t, err) + // + sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, + (&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID)) + assert.FatalError(t, err) + + crlCtx := provisioner.NewContextWithMethod(context.Background(), provisioner.RevokeMethod) + + var crlStore db.CertificateRevocationListInfo + var revokedList []db.RevokedCertificateInfo + + type test struct { + auth *Authority + ctx context.Context + expected []string + err error + } + tests := map[string]func() test{ + "fail/empty-crl": func() test { + a := testAuthority(t, WithDatabase(&db.MockAuthDB{ + MUseToken: func(id, tok string) (bool, error) { + return true, nil + }, + MGetCertificate: func(sn string) (*x509.Certificate, error) { + return nil, errors.New("not found") + }, + MStoreCRL: func(i *db.CertificateRevocationListInfo) error { + crlStore = *i + return nil + }, + MGetCRL: func() (*db.CertificateRevocationListInfo, error) { + return nil, database.ErrNotFound + }, + MGetRevokedCertificates: func() (*[]db.RevokedCertificateInfo, error) { + return &revokedList, nil + }, + MRevoke: func(rci *db.RevokedCertificateInfo) error { + revokedList = append(revokedList, *rci) + return nil + }, + })) + a.config.CRL = &config.CRLConfig{ + Enabled: true, + } + + return test{ + auth: a, + ctx: crlCtx, + expected: nil, + err: database.ErrNotFound, + } + }, + "ok/crl-full": func() test { + a := testAuthority(t, WithDatabase(&db.MockAuthDB{ + MUseToken: func(id, tok string) (bool, error) { + return true, nil + }, + MGetCertificate: func(sn string) (*x509.Certificate, error) { + return nil, errors.New("not found") + }, + MStoreCRL: func(i *db.CertificateRevocationListInfo) error { + crlStore = *i + return nil + }, + MGetCRL: func() (*db.CertificateRevocationListInfo, error) { + return &crlStore, nil + }, + MGetRevokedCertificates: func() (*[]db.RevokedCertificateInfo, error) { + return &revokedList, nil + }, + MRevoke: func(rci *db.RevokedCertificateInfo) error { + revokedList = append(revokedList, *rci) + return nil + }, + })) + a.config.CRL = &config.CRLConfig{ + Enabled: true, + GenerateOnRevoke: true, + } + + var ex []string + + for i := 0; i < 100; i++ { + sn := fmt.Sprintf("%v", i) + + cl := jose.Claims{ + Subject: fmt.Sprintf("sn-%v", i), + Issuer: validIssuer, + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), + Audience: validAudience, + ID: sn, + } + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() + assert.FatalError(t, err) + err = a.Revoke(crlCtx, &RevokeOptions{ + Serial: sn, + ReasonCode: reasonCode, + Reason: reason, + OTT: raw, + }) + + assert.FatalError(t, err) + + ex = append(ex, sn) + } + + return test{ + auth: a, + ctx: crlCtx, + expected: ex, + } + }, + } + for name, f := range tests { + tc := f() + t.Run(name, func(t *testing.T) { + if crlBytes, err := tc.auth.GetCertificateRevocationList(); err == nil { + crl, parseErr := x509.ParseCRL(crlBytes) + if parseErr != nil { + t.Errorf("x509.ParseCertificateRequest() error = %v, wantErr %v", parseErr, nil) + return + } + + var cmpList []string + for _, c := range crl.TBSCertList.RevokedCertificates { + cmpList = append(cmpList, c.SerialNumber.String()) + } + + assert.Equals(t, cmpList, tc.expected) + } else { + assert.NotNil(t, tc.err, err.Error()) + } + }) + } +} diff --git a/authority/webhook.go b/authority/webhook.go new file mode 100644 index 00000000..d887e077 --- /dev/null +++ b/authority/webhook.go @@ -0,0 +1,8 @@ +package authority + +import "github.com/smallstep/certificates/webhook" + +type webhookController interface { + Enrich(*webhook.RequestBody) error + Authorize(*webhook.RequestBody) error +} diff --git a/authority/webhook_test.go b/authority/webhook_test.go new file mode 100644 index 00000000..b80c8f66 --- /dev/null +++ b/authority/webhook_test.go @@ -0,0 +1,27 @@ +package authority + +import ( + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/webhook" +) + +type mockWebhookController struct { + enrichErr error + authorizeErr error + templateData provisioner.WebhookSetter + respData map[string]any +} + +var _ webhookController = &mockWebhookController{} + +func (wc *mockWebhookController) Enrich(req *webhook.RequestBody) error { + for key, data := range wc.respData { + wc.templateData.SetWebhook(key, data) + } + + return wc.enrichErr +} + +func (wc *mockWebhookController) Authorize(req *webhook.RequestBody) error { + return wc.authorizeErr +} diff --git a/ca/acmeClient.go b/ca/acmeClient.go index 2bbb0d6a..1c195efd 100644 --- a/ca/acmeClient.go +++ b/ca/acmeClient.go @@ -52,6 +52,7 @@ func NewACMEClient(endpoint string, contact []string, opts ...ClientOption) (*AC if err != nil { return nil, errors.Wrapf(err, "client GET %s failed", endpoint) } + defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, readACMEError(resp.Body) } @@ -80,6 +81,7 @@ func NewACMEClient(endpoint string, contact []string, opts ...ClientOption) (*AC if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, readACMEError(resp.Body) } @@ -111,6 +113,7 @@ func (c *ACMEClient) GetNonce() (string, error) { if err != nil { return "", errors.Wrapf(err, "client GET %s failed", c.dir.NewNonce) } + defer resp.Body.Close() if resp.StatusCode >= 400 { return "", readACMEError(resp.Body) } @@ -198,6 +201,7 @@ func (c *ACMEClient) NewOrder(payload []byte) (*acme.Order, error) { if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, readACMEError(resp.Body) } @@ -218,6 +222,7 @@ func (c *ACMEClient) GetChallenge(url string) (*acme.Challenge, error) { if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, readACMEError(resp.Body) } @@ -237,6 +242,7 @@ func (c *ACMEClient) ValidateChallenge(url string) error { if err != nil { return err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return readACMEError(resp.Body) } @@ -250,6 +256,7 @@ func (c *ACMEClient) ValidateWithPayload(url string, payload []byte) error { if err != nil { return err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return readACMEError(resp.Body) } @@ -262,6 +269,7 @@ func (c *ACMEClient) GetAuthz(url string) (*acme.Authorization, error) { if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, readACMEError(resp.Body) } @@ -279,6 +287,7 @@ func (c *ACMEClient) GetOrder(url string) (*acme.Order, error) { if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, readACMEError(resp.Body) } @@ -302,6 +311,7 @@ func (c *ACMEClient) FinalizeOrder(url string, csr *x509.CertificateRequest) err if err != nil { return err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return readACMEError(resp.Body) } @@ -314,6 +324,7 @@ func (c *ACMEClient) GetCertificate(url string) (*x509.Certificate, []*x509.Cert if err != nil { return nil, nil, err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, nil, readACMEError(resp.Body) } @@ -350,6 +361,7 @@ func (c *ACMEClient) GetAccountOrders() ([]string, error) { if err != nil { return nil, err } + defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, readACMEError(resp.Body) } diff --git a/ca/acmeClient_test.go b/ca/acmeClient_test.go index 034af0f6..77d380f9 100644 --- a/ca/acmeClient_test.go +++ b/ca/acmeClient_test.go @@ -1359,7 +1359,7 @@ func TestACMEClient_GetCertificate(t *testing.T) { Type: "Certificate", Bytes: leaf.Raw, }) - // nolint:gocritic + //nolint:gocritic certBytes := append(leafb, leafb...) certBytes = append(certBytes, leafb...) ac := &ACMEClient{ diff --git a/ca/adminClient.go b/ca/adminClient.go index 6532b000..cde197af 100644 --- a/ca/adminClient.go +++ b/ca/adminClient.go @@ -116,7 +116,6 @@ func (c *AdminClient) generateAdminToken(aud *url.URL) (string, error) { } return tok.SignedString(c.x5cJWK.Algorithm, c.x5cJWK.Key) - } func (c *AdminClient) retryOnError(r *http.Response) bool { @@ -1102,6 +1101,103 @@ retry: return nil } +func (c *AdminClient) CreateProvisionerWebhook(provisionerName string, wh *linkedca.Webhook) (*linkedca.Webhook, error) { + var retried bool + body, err := protojson.Marshal(wh) + if err != nil { + return nil, fmt.Errorf("error marshaling request: %w", err) + } + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", provisionerName, "webhooks")}) + tok, err := c.generateAdminToken(u) + if err != nil { + return nil, fmt.Errorf("error generating admin token: %w", err) + } +retry: + req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("creating POST %s request failed: %w", u, err) + } + req.Header.Add("Authorization", tok) + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("client POST %s failed: %w", u, err) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return nil, readAdminError(resp.Body) + } + var webhook = new(linkedca.Webhook) + if err := readProtoJSON(resp.Body, webhook); err != nil { + return nil, fmt.Errorf("error reading %s: %w", u, err) + } + return webhook, nil +} + +func (c *AdminClient) UpdateProvisionerWebhook(provisionerName string, wh *linkedca.Webhook) (*linkedca.Webhook, error) { + var retried bool + body, err := protojson.Marshal(wh) + if err != nil { + return nil, fmt.Errorf("error marshaling request: %w", err) + } + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", provisionerName, "webhooks", wh.Name)}) + tok, err := c.generateAdminToken(u) + if err != nil { + return nil, fmt.Errorf("error generating admin token: %w", err) + } +retry: + req, err := http.NewRequest(http.MethodPut, u.String(), bytes.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("creating PUT %s request failed: %w", u, err) + } + req.Header.Add("Authorization", tok) + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("client PUT %s failed: %w", u, err) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return nil, readAdminError(resp.Body) + } + var webhook = new(linkedca.Webhook) + if err := readProtoJSON(resp.Body, webhook); err != nil { + return nil, fmt.Errorf("error reading %s: %w", u, err) + } + return webhook, nil +} + +func (c *AdminClient) DeleteProvisionerWebhook(provisionerName, webhookName string) error { + var retried bool + u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", provisionerName, "webhooks", webhookName)}) + tok, err := c.generateAdminToken(u) + if err != nil { + return fmt.Errorf("error generating admin token: %w", err) + } +retry: + req, err := http.NewRequest(http.MethodDelete, u.String(), http.NoBody) + if err != nil { + return fmt.Errorf("creating DELETE %s request failed: %w", u, err) + } + req.Header.Add("Authorization", tok) + resp, err := c.client.Do(req) + if err != nil { + return fmt.Errorf("client DELETE %s failed: %w", u, err) + } + if resp.StatusCode >= 400 { + if !retried && c.retryOnError(resp) { + retried = true + goto retry + } + return readAdminError(resp.Body) + } + return nil +} + func readAdminError(r io.ReadCloser) error { // TODO: not all errors can be read (i.e. 404); seems to be a bigger issue defer r.Close() diff --git a/ca/bootstrap_test.go b/ca/bootstrap_test.go index 2a837a3d..974ba1f1 100644 --- a/ca/bootstrap_test.go +++ b/ca/bootstrap_test.go @@ -200,7 +200,7 @@ func TestBootstrap(t *testing.T) { } } -// nolint:gosec // insecure test servers +//nolint:gosec // insecure test servers func TestBootstrapServerWithoutMTLS(t *testing.T) { srv := startCABootstrapServer() defer srv.Close() @@ -246,6 +246,7 @@ func TestBootstrapServerWithoutMTLS(t *testing.T) { expected := &http.Server{ TLSConfig: got.TLSConfig, } + //nolint:govet // not comparing errors if !reflect.DeepEqual(got, expected) { t.Errorf("BootstrapServer() = %v, want %v", got, expected) } @@ -257,7 +258,7 @@ func TestBootstrapServerWithoutMTLS(t *testing.T) { } } -// nolint:gosec // insecure test servers +//nolint:gosec // insecure test servers func TestBootstrapServerWithMTLS(t *testing.T) { srv := startCABootstrapServer() defer srv.Close() @@ -303,6 +304,7 @@ func TestBootstrapServerWithMTLS(t *testing.T) { expected := &http.Server{ TLSConfig: got.TLSConfig, } + //nolint:govet // not comparing errors if !reflect.DeepEqual(got, expected) { t.Errorf("BootstrapServer() = %v, want %v", got, expected) } @@ -407,7 +409,7 @@ func TestBootstrapClientServerRotation(t *testing.T) { // Create bootstrap server token := generateBootstrapToken(caURL, "127.0.0.1", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7") - // nolint:gosec // insecure test server + //nolint:gosec // insecure test server server, err := BootstrapServer(context.Background(), token, &http.Server{ Addr: ":0", Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { @@ -526,7 +528,7 @@ func TestBootstrapClientServerFederation(t *testing.T) { // Create bootstrap server token := generateBootstrapToken(caURL1, "127.0.0.1", "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7") - // nolint:gosec // insecure test server + //nolint:gosec // insecure test server server, err := BootstrapServer(context.Background(), token, &http.Server{ Addr: ":0", Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { diff --git a/ca/ca.go b/ca/ca.go index bddcab79..880f7e46 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -156,17 +156,26 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { opts = append(opts, authority.WithDatabase(ca.opts.database)) } + if ca.opts.quiet { + opts = append(opts, authority.WithQuietInit()) + } + + webhookTransport := http.DefaultTransport.(*http.Transport).Clone() + opts = append(opts, authority.WithWebhookClient(&http.Client{Transport: webhookTransport})) + auth, err := authority.New(cfg, opts...) if err != nil { return nil, err } ca.auth = auth - tlsConfig, err := ca.getTLSConfig(auth) + tlsConfig, clientTLSConfig, err := ca.getTLSConfig(auth) if err != nil { return nil, err } + webhookTransport.TLSClientConfig = clientTLSConfig + // Using chi as the main router mux := chi.NewRouter() handler := http.Handler(mux) @@ -220,8 +229,14 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { if adminDB != nil { acmeAdminResponder := adminAPI.NewACMEAdminResponder() policyAdminResponder := adminAPI.NewPolicyAdminResponder() + webhookAdminResponder := adminAPI.NewWebhookAdminResponder() mux.Route("/admin", func(r chi.Router) { - adminAPI.Route(r, acmeAdminResponder, policyAdminResponder) + adminAPI.Route( + r, + adminAPI.WithACMEResponder(acmeAdminResponder), + adminAPI.WithPolicyResponder(policyAdminResponder), + adminAPI.WithWebhookResponder(webhookAdminResponder), + ) }) } } @@ -334,7 +349,7 @@ func (ca *CA) Run() error { if step.Contexts().GetCurrent() != nil { log.Printf("Current context: %s", step.Contexts().GetCurrent().Name) } - log.Printf("Config file: %s", ca.opts.configFile) + log.Printf("Config file: %s", ca.getConfigFileOutput()) baseURL := fmt.Sprintf("https://%s%s", authorityInfo.DNSNames[0], ca.config.Address[strings.LastIndex(ca.config.Address, ":"):]) @@ -456,13 +471,13 @@ func (ca *CA) Reload() error { return nil } -// getTLSConfig returns a TLSConfig for the CA server with a self-renewing -// server certificate. -func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) { +// get TLSConfig returns separate TLSConfigs for server and client with the +// same self-renewing certificate. +func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, *tls.Config, error) { // Create initial TLS certificate tlsCrt, err := auth.GetTLSCertificate() if err != nil { - return nil, err + return nil, nil, err } // Start tls renewer with the new certificate. @@ -473,15 +488,15 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) { ca.renewer, err = NewTLSRenewer(tlsCrt, auth.GetTLSCertificate) if err != nil { - return nil, err + return nil, nil, err } ca.renewer.Run() - var tlsConfig *tls.Config + var serverTLSConfig *tls.Config if ca.config.TLS != nil { - tlsConfig = ca.config.TLS.TLSConfig() + serverTLSConfig = ca.config.TLS.TLSConfig() } else { - tlsConfig = &tls.Config{ + serverTLSConfig = &tls.Config{ MinVersion: tls.VersionTLS12, } } @@ -493,13 +508,24 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) { // first entry in the Certificates attribute; by setting the attribute to // empty we are implicitly forcing GetCertificate to be the only mechanism // by which the server can find it's own leaf Certificate. - tlsConfig.Certificates = []tls.Certificate{} - tlsConfig.GetCertificate = ca.renewer.GetCertificateForCA + serverTLSConfig.Certificates = []tls.Certificate{} + + clientTLSConfig := serverTLSConfig.Clone() + + serverTLSConfig.GetCertificate = ca.renewer.GetCertificateForCA + clientTLSConfig.GetClientCertificate = ca.renewer.GetClientCertificate // initialize a certificate pool with root CA certificates to trust when doing mTLS. certPool := x509.NewCertPool() + // initialize a certificate pool with root CA certificates to trust when connecting + // to webhook servers + rootCAsPool, err := x509.SystemCertPool() + if err != nil { + return nil, nil, err + } for _, crt := range auth.GetRootCertificates() { certPool.AddCert(crt) + rootCAsPool.AddCert(crt) } // adding the intermediate CA certificates to the pool will allow clients that @@ -509,16 +535,19 @@ func (ca *CA) getTLSConfig(auth *authority.Authority) (*tls.Config, error) { for _, certBytes := range intermediates { cert, err := x509.ParseCertificate(certBytes) if err != nil { - return nil, err + return nil, nil, err } certPool.AddCert(cert) + rootCAsPool.AddCert(cert) } // Add support for mutual tls to renew certificates - tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven - tlsConfig.ClientCAs = certPool + serverTLSConfig.ClientAuth = tls.VerifyClientCertIfGiven + serverTLSConfig.ClientCAs = certPool + + clientTLSConfig.RootCAs = rootCAsPool - return tlsConfig, nil + return serverTLSConfig, clientTLSConfig, nil } // shouldServeSCEPEndpoints returns if the CA should be @@ -529,9 +558,9 @@ func (ca *CA) shouldServeSCEPEndpoints() bool { return ca.auth.GetSCEPService() != nil } -// nolint // ignore linters to allow keeping this function around for debugging +//nolint:unused // useful for debugging func dumpRoutes(mux chi.Routes) { - // helpful routine for logging all routes // + // helpful routine for logging all routes walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { fmt.Printf("%s %s\n", method, route) return nil @@ -540,3 +569,10 @@ func dumpRoutes(mux chi.Routes) { fmt.Printf("Logging err: %s\n", err.Error()) } } + +func (ca *CA) getConfigFileOutput() string { + if ca.config.WasLoadedFromFile() { + return ca.config.Filepath() + } + return "loaded from token" +} diff --git a/ca/ca_test.go b/ca/ca_test.go index e76ca8ff..7ad25cc6 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -5,7 +5,7 @@ import ( "context" "crypto" "crypto/rand" - "crypto/sha1" // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + "crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 "crypto/tls" "crypto/x509" "crypto/x509/pkix" @@ -66,7 +66,7 @@ func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { return nil, errors.Wrap(err, "error unmarshaling public key") } - // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 hash := sha1.Sum(info.SubjectPublicKey.Bytes) return hash[:], nil } diff --git a/ca/client.go b/ca/client.go index 19fcd0bd..8519e5c5 100644 --- a/ca/client.go +++ b/ca/client.go @@ -56,7 +56,7 @@ func newClient(transport http.RoundTripper) *uaClient { } } -// nolint:gosec // used in bootstrap protocol +//nolint:gosec // used in bootstrap protocol func newInsecureClient() *uaClient { return &uaClient{ Client: &http.Client{ @@ -120,9 +120,7 @@ type clientOptions struct { } func (o *clientOptions) apply(opts []ClientOption) (err error) { - if err = o.applyDefaultIdentity(); err != nil { - return - } + o.applyDefaultIdentity() for _, fn := range opts { if err = fn(o); err != nil { return @@ -133,26 +131,25 @@ func (o *clientOptions) apply(opts []ClientOption) (err error) { // applyDefaultIdentity sets the options for the default identity if the // identity file is present. The identity is enabled by default. -func (o *clientOptions) applyDefaultIdentity() error { +func (o *clientOptions) applyDefaultIdentity() { if DisableIdentity { - return nil + return } // Do not load an identity if something fails i, err := identity.LoadDefaultIdentity() if err != nil { - return nil + return } if err := i.Validate(); err != nil { - return nil + return } crt, err := i.TLSCertificate() if err != nil { - return nil + return } o.certificate = crt o.getClientCertificate = i.GetClientCertificateFunc() - return nil } // checkTransport checks if other ways to set up a transport have been provided. @@ -241,13 +238,13 @@ func WithTransport(tr http.RoundTripper) ClientOption { } // WithInsecure adds a insecure transport that bypasses TLS verification. -// nolint:gosec // insecure option func WithInsecure() ClientOption { return func(o *clientOptions) error { o.transport = &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ - MinVersion: tls.VersionTLS12, + MinVersion: tls.VersionTLS12, + //nolint:gosec // insecure option InsecureSkipVerify: true, }, } @@ -1139,7 +1136,7 @@ retry: var check api.SSHCheckPrincipalResponse if err := readJSON(resp.Body, &check); err != nil { return nil, errs.Wrapf(http.StatusInternalServerError, err, "error reading %s response", - []interface{}{u, errs.WithMessage("Failed to parse response from /ssh/check-host endpoint")}) + []any{u, errs.WithMessage("Failed to parse response from /ssh/check-host endpoint")}...) } return &check, nil } @@ -1203,6 +1200,7 @@ func (c *Client) RootFingerprint() (string, error) { if err != nil { return "", errors.Wrapf(err, "client GET %s failed", u) } + defer resp.Body.Close() if resp.TLS == nil || len(resp.TLS.VerifiedChains) == 0 { return "", errors.New("missing verified chains") } diff --git a/ca/client_test.go b/ca/client_test.go index 48aa1488..dff7fd41 100644 --- a/ca/client_test.go +++ b/ca/client_test.go @@ -519,9 +519,10 @@ func TestClient_Renew(t *testing.T) { t.Errorf("Client.Renew() = %v, want nil", got) } - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tt.responseCode) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tt.responseCode) + } assert.HasPrefix(t, err.Error(), tt.err.Error()) default: if !reflect.DeepEqual(got, tt.response) { @@ -587,9 +588,10 @@ func TestClient_RenewWithToken(t *testing.T) { t.Errorf("Client.RenewWithToken() = %v, want nil", got) } - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tt.responseCode) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tt.responseCode) + } assert.HasPrefix(t, err.Error(), tt.err.Error()) default: if !reflect.DeepEqual(got, tt.response) { @@ -656,9 +658,10 @@ func TestClient_Rekey(t *testing.T) { t.Errorf("Client.Renew() = %v, want nil", got) } - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tt.responseCode) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tt.responseCode) + } assert.HasPrefix(t, err.Error(), tt.err.Error()) default: if !reflect.DeepEqual(got, tt.response) { @@ -777,9 +780,10 @@ func TestClient_ProvisionerKey(t *testing.T) { t.Errorf("Client.ProvisionerKey() = %v, want nil", got) } - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tt.responseCode) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tt.responseCode) + } assert.HasPrefix(t, tt.err.Error(), err.Error()) default: if !reflect.DeepEqual(got, tt.response) { @@ -836,9 +840,10 @@ func TestClient_Roots(t *testing.T) { if got != nil { t.Errorf("Client.Roots() = %v, want nil", got) } - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tt.responseCode) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tt.responseCode) + } assert.HasPrefix(t, err.Error(), tt.err.Error()) default: if !reflect.DeepEqual(got, tt.response) { @@ -894,9 +899,10 @@ func TestClient_Federation(t *testing.T) { if got != nil { t.Errorf("Client.Federation() = %v, want nil", got) } - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tt.responseCode) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tt.responseCode) + } assert.HasPrefix(t, tt.err.Error(), err.Error()) default: if !reflect.DeepEqual(got, tt.response) { @@ -956,9 +962,10 @@ func TestClient_SSHRoots(t *testing.T) { if got != nil { t.Errorf("Client.SSHKeys() = %v, want nil", got) } - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tt.responseCode) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tt.responseCode) + } assert.HasPrefix(t, tt.err.Error(), err.Error()) default: if !reflect.DeepEqual(got, tt.response) { @@ -1118,9 +1125,10 @@ func TestClient_SSHBastion(t *testing.T) { t.Errorf("Client.SSHBastion() = %v, want nil", got) } if tt.responseCode != 200 { - sc, ok := err.(render.StatusCodedError) - assert.Fatal(t, ok, "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tt.responseCode) + var sc render.StatusCodedError + if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { + assert.Equals(t, sc.StatusCode(), tt.responseCode) + } assert.HasPrefix(t, err.Error(), tt.err.Error()) } default: diff --git a/ca/identity/client.go b/ca/identity/client.go index 4b0aee82..f6c8c213 100644 --- a/ca/identity/client.go +++ b/ca/identity/client.go @@ -82,7 +82,6 @@ func LoadClient() (*Client, error) { Transport: tr, }, }, nil - } type defaultsConfig struct { diff --git a/ca/identity/client_test.go b/ca/identity/client_test.go index 14e6da6c..2ebeb15d 100644 --- a/ca/identity/client_test.go +++ b/ca/identity/client_test.go @@ -242,7 +242,7 @@ func Test_defaultsConfig_Validate(t *testing.T) { } } -// nolint:staticcheck,gocritic +//nolint:staticcheck,gocritic func equalPools(a, b *x509.CertPool) bool { if reflect.DeepEqual(a, b) { return true diff --git a/ca/identity/identity.go b/ca/identity/identity.go index 2a6b4c39..755d270a 100644 --- a/ca/identity/identity.go +++ b/ca/identity/identity.go @@ -261,6 +261,7 @@ func (i *Identity) GetClientCertificateFunc() func(*tls.CertificateRequestInfo) // GetCertPool returns a x509.CertPool if the identity defines a custom root. func (i *Identity) GetCertPool() (*x509.CertPool, error) { if i.Root == "" { + //nolint:nilnil // legacy return nil, nil } b, err := os.ReadFile(i.Root) diff --git a/ca/identity/identity_test.go b/ca/identity/identity_test.go index eb32328a..9a2422b3 100644 --- a/ca/identity/identity_test.go +++ b/ca/identity/identity_test.go @@ -345,7 +345,7 @@ func TestIdentity_GetCertPool(t *testing.T) { return } if got != nil { - // nolint:staticcheck // we don't have a different way to check + //nolint:staticcheck // we don't have a different way to check // the certificates in the pool. subjects := got.Subjects() if !reflect.DeepEqual(subjects, tt.wantSubjects) { diff --git a/ca/provisioner.go b/ca/provisioner.go index c1879c86..d5b23f38 100644 --- a/ca/provisioner.go +++ b/ca/provisioner.go @@ -182,19 +182,17 @@ func loadProvisionerJWKByKid(client *Client, kid string, password []byte) (*jose // loadProvisionerJWKByName retrieves the list of provisioners and encrypted key then // returns the key of the first provisioner with a matching name that can be successfully // decrypted with the specified password. -func loadProvisionerJWKByName(client *Client, name string, password []byte) (key *jose.JSONWebKey, err error) { +func loadProvisionerJWKByName(client *Client, name string, password []byte) (*jose.JSONWebKey, error) { provisioners, err := getProvisioners(client) if err != nil { - err = errors.Wrap(err, "error getting the provisioners") - return + return nil, errors.Wrap(err, "error getting the provisioners") } for _, provisioner := range provisioners { if provisioner.GetName() == name { if _, encryptedKey, ok := provisioner.GetEncryptedKey(); ok { - key, err = decryptProvisionerJWK(encryptedKey, password) - if err == nil { - return + if key, err := decryptProvisionerJWK(encryptedKey, password); err == nil { + return key, nil } } } diff --git a/ca/renew.go b/ca/renew.go index a913e59c..ea4c5764 100644 --- a/ca/renew.go +++ b/ca/renew.go @@ -193,7 +193,7 @@ func (r *TLSRenewer) nextRenewDuration(notAfter time.Time) time.Duration { return d } -// nolint:gosec // not used for cryptographic security +//nolint:gosec // not used for cryptographic security func mathRandInt63n(n int64) int64 { return rand.Int63n(n) } diff --git a/ca/tls.go b/ca/tls.go index b4d54952..282f9778 100644 --- a/ca/tls.go +++ b/ca/tls.go @@ -105,8 +105,8 @@ func (c *Client) getClientTLSConfig(ctx context.Context, sign *api.SignResponse, } tr := getDefaultTransport(tlsConfig) - // Use mutable tls.Config on renew - tr.DialTLS = c.buildDialTLS(tlsCtx) // nolint:staticcheck,gocritic + //nolint:staticcheck // Use mutable tls.Config on renew + tr.DialTLS = c.buildDialTLS(tlsCtx) // tr.DialTLSContext = c.buildDialTLSContext(tlsCtx) renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) @@ -153,8 +153,8 @@ func (c *Client) GetServerTLSConfig(ctx context.Context, sign *api.SignResponse, // Update renew function with transport tr := getDefaultTransport(tlsConfig) - // Use mutable tls.Config on renew - tr.DialTLS = c.buildDialTLS(tlsCtx) // nolint:staticcheck,gocritic + //nolint:staticcheck // Use mutable tls.Config on renew + tr.DialTLS = c.buildDialTLS(tlsCtx) // tr.DialTLSContext = c.buildDialTLSContext(tlsCtx) renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) @@ -194,8 +194,7 @@ func (c *Client) buildDialTLS(ctx *TLSOptionCtx) func(network, addr string) (net } } -// buildDialTLSContext returns an implementation of DialTLSContext callback in http.Transport. -// nolint:unused,gocritic +//nolint:unused // buildDialTLSContext returns an implementation of DialTLSContext callback in http.Transport. func (c *Client) buildDialTLSContext(tlsCtx *TLSOptionCtx) func(ctx context.Context, network, addr string) (net.Conn, error) { return func(ctx context.Context, network, addr string) (net.Conn, error) { d := getDefaultDialer() @@ -253,8 +252,7 @@ func TLSCertificate(sign *api.SignResponse, pk crypto.PrivateKey) (*tls.Certific return nil, err } - // nolint:gocritic - // using a new variable for clarity + //nolint:gocritic // using a new variable for clarity chain := append(certPEM, caPEM...) cert, err := tls.X509KeyPair(chain, keyPEM) if err != nil { diff --git a/ca/tls_options_test.go b/ca/tls_options_test.go index 65086315..7dea3dc8 100644 --- a/ca/tls_options_test.go +++ b/ca/tls_options_test.go @@ -13,7 +13,7 @@ import ( "github.com/smallstep/certificates/api" ) -// nolint:gosec // test tls config +//nolint:gosec // test tls config func Test_newTLSOptionCtx(t *testing.T) { client, err := NewClient("https://ca.smallstep.com", WithTransport(http.DefaultTransport)) if err != nil { @@ -41,7 +41,7 @@ func Test_newTLSOptionCtx(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestTLSOptionCtx_apply(t *testing.T) { fail := func() TLSOption { return func(ctx *TLSOptionCtx) error { @@ -78,7 +78,7 @@ func TestTLSOptionCtx_apply(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestRequireAndVerifyClientCert(t *testing.T) { tests := []struct { name string @@ -103,7 +103,7 @@ func TestRequireAndVerifyClientCert(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestVerifyClientCertIfGiven(t *testing.T) { tests := []struct { name string @@ -128,7 +128,7 @@ func TestVerifyClientCertIfGiven(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestAddRootCA(t *testing.T) { cert := parseCertificate(rootPEM) pool := x509.NewCertPool() @@ -161,7 +161,7 @@ func TestAddRootCA(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestAddClientCA(t *testing.T) { cert := parseCertificate(rootPEM) pool := x509.NewCertPool() @@ -194,7 +194,7 @@ func TestAddClientCA(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestAddRootsToRootCAs(t *testing.T) { ca := startCATestServer() defer ca.Close() @@ -249,7 +249,7 @@ func TestAddRootsToRootCAs(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestAddRootsToClientCAs(t *testing.T) { ca := startCATestServer() defer ca.Close() @@ -304,7 +304,7 @@ func TestAddRootsToClientCAs(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestAddFederationToRootCAs(t *testing.T) { ca := startCATestServer() defer ca.Close() @@ -369,7 +369,7 @@ func TestAddFederationToRootCAs(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestAddFederationToClientCAs(t *testing.T) { ca := startCATestServer() defer ca.Close() @@ -434,7 +434,7 @@ func TestAddFederationToClientCAs(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestAddRootsToCAs(t *testing.T) { ca := startCATestServer() defer ca.Close() @@ -489,7 +489,7 @@ func TestAddRootsToCAs(t *testing.T) { } } -// nolint:gosec // test tls config +//nolint:gosec // test tls config func TestAddFederationToCAs(t *testing.T) { ca := startCATestServer() defer ca.Close() @@ -554,7 +554,7 @@ func TestAddFederationToCAs(t *testing.T) { } } -// nolint:staticcheck,gocritic +//nolint:staticcheck,gocritic func equalPools(a, b *x509.CertPool) bool { if reflect.DeepEqual(a, b) { return true diff --git a/cas/apiv1/requests.go b/cas/apiv1/requests.go index d93cf38d..fdbb285e 100644 --- a/cas/apiv1/requests.go +++ b/cas/apiv1/requests.go @@ -81,6 +81,7 @@ type RenewCertificateRequest struct { CSR *x509.CertificateRequest Lifetime time.Duration Backdate time.Duration + Token string RequestID string } @@ -154,3 +155,13 @@ type CreateCertificateAuthorityResponse struct { PrivateKey crypto.PrivateKey Signer crypto.Signer } + +// CreateCRLRequest is the request to create a Certificate Revocation List. +type CreateCRLRequest struct { + RevocationList *x509.RevocationList +} + +// CreateCRLResponse is the response to a Certificate Revocation List request. +type CreateCRLResponse struct { + CRL []byte //the CRL in DER format +} diff --git a/cas/apiv1/services.go b/cas/apiv1/services.go index c8a8b0e9..bca24d96 100644 --- a/cas/apiv1/services.go +++ b/cas/apiv1/services.go @@ -14,6 +14,12 @@ type CertificateAuthorityService interface { RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error) } +// CertificateAuthorityCRLGenerator is an optional interface implemented by CertificateAuthorityService +// that has a method to create a CRL +type CertificateAuthorityCRLGenerator interface { + CreateCRL(req *CreateCRLRequest) (*CreateCRLResponse, error) +} + // CertificateAuthorityGetter is an interface implemented by a // CertificateAuthorityService that has a method to get the root certificate. type CertificateAuthorityGetter interface { @@ -59,14 +65,13 @@ func (t Type) String() string { return strings.ToLower(string(t)) } -// ErrNotImplemented is the type of error returned if an operation is not -// implemented. -type ErrNotImplemented struct { +// NotImplementedError is the type of error returned if an operation is not implemented. +type NotImplementedError struct { Message string } -// ErrNotImplemented implements the error interface. -func (e ErrNotImplemented) Error() string { +// Error implements the error interface. +func (e NotImplementedError) Error() string { if e.Message != "" { return e.Message } @@ -75,6 +80,26 @@ func (e ErrNotImplemented) Error() string { // StatusCode implements the StatusCoder interface and returns the HTTP 501 // error. -func (e ErrNotImplemented) StatusCode() int { +func (e NotImplementedError) StatusCode() int { return http.StatusNotImplemented } + +// ValidationError is the type of error returned if request is not properly +// validated. +type ValidationError struct { + Message string +} + +// NotImplementedError implements the error interface. +func (e ValidationError) Error() string { + if e.Message != "" { + return e.Message + } + return "bad request" +} + +// StatusCode implements the StatusCoder interface and returns the HTTP 400 +// error. +func (e ValidationError) StatusCode() int { + return http.StatusBadRequest +} diff --git a/cas/apiv1/services_test.go b/cas/apiv1/services_test.go index eb7d502e..9289de76 100644 --- a/cas/apiv1/services_test.go +++ b/cas/apiv1/services_test.go @@ -24,7 +24,7 @@ func TestType_String(t *testing.T) { } } -func TestErrNotImplemented_Error(t *testing.T) { +func TestNotImplementedError_Error(t *testing.T) { type fields struct { Message string } @@ -38,17 +38,17 @@ func TestErrNotImplemented_Error(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - e := ErrNotImplemented{ + e := NotImplementedError{ Message: tt.fields.Message, } if got := e.Error(); got != tt.want { - t.Errorf("ErrNotImplemented.Error() = %v, want %v", got, tt.want) + t.Errorf("NotImplementedError.Error() = %v, want %v", got, tt.want) } }) } } -func TestErrNotImplemented_StatusCode(t *testing.T) { +func TestNotImplementedError_StatusCode(t *testing.T) { type fields struct { Message string } @@ -62,11 +62,59 @@ func TestErrNotImplemented_StatusCode(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := ErrNotImplemented{ + s := NotImplementedError{ Message: tt.fields.Message, } if got := s.StatusCode(); got != tt.want { - t.Errorf("ErrNotImplemented.StatusCode() = %v, want %v", got, tt.want) + t.Errorf("NotImplementedError.StatusCode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestValidationError_Error(t *testing.T) { + type fields struct { + Message string + } + tests := []struct { + name string + fields fields + want string + }{ + {"default", fields{""}, "bad request"}, + {"with message", fields{"token is empty"}, "token is empty"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := ValidationError{ + Message: tt.fields.Message, + } + if got := e.Error(); got != tt.want { + t.Errorf("ValidationError.Error() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestValidationError_StatusCode(t *testing.T) { + type fields struct { + Message string + } + tests := []struct { + name string + fields fields + want int + }{ + {"default", fields{""}, 400}, + {"with message", fields{"token is empty"}, 400}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := ValidationError{ + Message: tt.fields.Message, + } + if got := e.StatusCode(); got != tt.want { + t.Errorf("ValidationError.StatusCode() = %v, want %v", got, tt.want) } }) } diff --git a/cas/cloudcas/certificate.go b/cas/cloudcas/certificate.go index a78deac4..64805d96 100644 --- a/cas/cloudcas/certificate.go +++ b/cas/cloudcas/certificate.go @@ -10,9 +10,9 @@ import ( "encoding/pem" "fmt" + pb "cloud.google.com/go/security/privateca/apiv1/privatecapb" "github.com/pkg/errors" kmsapi "go.step.sm/crypto/kms/apiv1" - pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1" ) var ( diff --git a/cas/cloudcas/certificate_test.go b/cas/cloudcas/certificate_test.go index 4e98fdf6..dfc3c229 100644 --- a/cas/cloudcas/certificate_test.go +++ b/cas/cloudcas/certificate_test.go @@ -14,8 +14,8 @@ import ( "reflect" "testing" + pb "cloud.google.com/go/security/privateca/apiv1/privatecapb" kmsapi "go.step.sm/crypto/kms/apiv1" - pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1" ) var ( diff --git a/cas/cloudcas/cloudcas.go b/cas/cloudcas/cloudcas.go index 34ff8506..c9c8364f 100644 --- a/cas/cloudcas/cloudcas.go +++ b/cas/cloudcas/cloudcas.go @@ -11,13 +11,13 @@ import ( "time" privateca "cloud.google.com/go/security/privateca/apiv1" + pb "cloud.google.com/go/security/privateca/apiv1/privatecapb" "github.com/google/uuid" gax "github.com/googleapis/gax-go/v2" "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" "go.step.sm/crypto/x509util" "google.golang.org/api/option" - pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" durationpb "google.golang.org/protobuf/types/known/durationpb" diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index e5fbf58e..d4e92a32 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -1,3 +1,6 @@ +//go:generate mockgen -package cloudcas -mock_names=CertificateAuthorityClient=MockCertificateAuthorityClient -destination mock_client_test.go github.com/smallstep/certificates/cas/cloudcas CertificateAuthorityClient +//go:generate mockgen -package cloudcas -mock_names=OperationsServer=MockOperationsServer -destination mock_operation_server_test.go cloud.google.com/go/longrunning/autogen/longrunningpb OperationsServer + package cloudcas import ( @@ -20,7 +23,9 @@ import ( "time" lroauto "cloud.google.com/go/longrunning/autogen" + "cloud.google.com/go/longrunning/autogen/longrunningpb" privateca "cloud.google.com/go/security/privateca/apiv1" + pb "cloud.google.com/go/security/privateca/apiv1/privatecapb" gomock "github.com/golang/mock/gomock" "github.com/google/uuid" gax "github.com/googleapis/gax-go/v2" @@ -28,8 +33,6 @@ import ( "github.com/smallstep/certificates/cas/apiv1" kmsapi "go.step.sm/crypto/kms/apiv1" "google.golang.org/api/option" - pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1" - longrunningpb "google.golang.org/genproto/googleapis/longrunning" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" @@ -104,7 +107,7 @@ MHcCAQEEIN51Rgg6YcQVLeCRzumdw4pjM3VWqFIdCbnsV3Up1e/goAoGCCqGSM49 AwEHoUQDQgAEjJIcDhvvxi7gu4aFkiW/8+E3BfPhmhXU5RlDQusre+MHXc7XYMtk Lm6PXPeTF1DNdS21Ju1G/j1yUykGJOmxkg== -----END EC PRIVATE KEY-----` - // nolint:unused,deadcode,gocritic + //nolint:unused,gocritic,varcheck testIntermediateKey = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIMMX/XkXGnRDD4fYu7Z4rHACdJn/iyOy2UTwsv+oZ0C+oAoGCCqGSM49 AwEHoUQDQgAE8u6rGAFj5CZpdzzMogLwUyCMnp0X9wtv4OKDRcpzkYf9PU5GuGA6 @@ -399,7 +402,7 @@ func TestNew_real(t *testing.T) { if v, ok := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS"); ok { os.Unsetenv("GOOGLE_APPLICATION_CREDENTIALS") t.Cleanup(func() { - os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", v) + t.Setenv("GOOGLE_APPLICATION_CREDENTIALS", v) }) } @@ -881,12 +884,12 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { fake.LROClient = client // Configure mocks - any := gomock.Any() + anee := gomock.Any() // ok root - m.EXPECT().GetCaPool(any, any).Return(nil, status.Error(codes.NotFound, "not found")) - m.EXPECT().CreateCaPool(any, any).Return(fake.CreateCaPoolOperation("CreateCaPool"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(nil, status.Error(codes.NotFound, "not found")) + m.EXPECT().CreateCaPool(anee, anee).Return(fake.CreateCaPoolOperation("CreateCaPool"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCaPool", Done: true, Result: &longrunningpb.Operation_Response{ @@ -895,8 +898,8 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -906,8 +909,8 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().EnableCertificateAuthority(any, any).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().EnableCertificateAuthority(anee, anee).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "EnableCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -919,9 +922,9 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { }, nil) // ok intermediate - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -930,15 +933,15 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: testIntermediateCsr, }, nil) - m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{ + m.EXPECT().CreateCertificate(anee, anee).Return(&pb.Certificate{ PemCertificate: testIntermediateCertificate, PemCertificateChain: []string{testRootCertificate}, }, nil) - m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().ActivateCertificateAuthority(anee, anee).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "ActivateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -948,8 +951,8 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().EnableCertificateAuthority(any, any).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().EnableCertificateAuthority(anee, anee).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "EnableCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -961,9 +964,9 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { }, nil) // ok intermediate local signer - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -972,11 +975,11 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: testIntermediateCsr, }, nil) - m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().ActivateCertificateAuthority(anee, anee).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "ActivateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -986,8 +989,8 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().EnableCertificateAuthority(any, any).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().EnableCertificateAuthority(anee, anee).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "EnableCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -999,9 +1002,9 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { }, nil) // ok create key - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1011,8 +1014,8 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().EnableCertificateAuthority(any, any).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().EnableCertificateAuthority(anee, anee).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "EnableCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1024,30 +1027,30 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { }, nil) // fail GetCaPool - m.EXPECT().GetCaPool(any, any).Return(nil, errTest) + m.EXPECT().GetCaPool(anee, anee).Return(nil, errTest) // fail CreateCaPool - m.EXPECT().GetCaPool(any, any).Return(nil, status.Error(codes.NotFound, "not found")) - m.EXPECT().CreateCaPool(any, any).Return(nil, errTest) + m.EXPECT().GetCaPool(anee, anee).Return(nil, status.Error(codes.NotFound, "not found")) + m.EXPECT().CreateCaPool(anee, anee).Return(nil, errTest) // fail CreateCaPool.Wait - m.EXPECT().GetCaPool(any, any).Return(nil, status.Error(codes.NotFound, "not found")) - m.EXPECT().CreateCaPool(any, any).Return(fake.CreateCaPoolOperation("CreateCaPool"), nil) - mos.EXPECT().GetOperation(any, any).Return(nil, errTest) + m.EXPECT().GetCaPool(anee, anee).Return(nil, status.Error(codes.NotFound, "not found")) + m.EXPECT().CreateCaPool(anee, anee).Return(fake.CreateCaPoolOperation("CreateCaPool"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(nil, errTest) // fail CreateCertificateAuthority - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(nil, errTest) + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(nil, errTest) // fail CreateCertificateAuthority.Wait - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(nil, errTest) + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(nil, errTest) // fail EnableCertificateAuthority - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1057,12 +1060,12 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().EnableCertificateAuthority(any, any).Return(nil, errTest) + m.EXPECT().EnableCertificateAuthority(anee, anee).Return(nil, errTest) // fail EnableCertificateAuthority.Wait - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1072,13 +1075,13 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().EnableCertificateAuthority(any, any).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) - mos.EXPECT().GetOperation(any, any).Return(nil, errTest) + m.EXPECT().EnableCertificateAuthority(anee, anee).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(nil, errTest) // fail EnableCertificateAuthority intermediate - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1087,15 +1090,15 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: testIntermediateCsr, }, nil) - m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{ + m.EXPECT().CreateCertificate(anee, anee).Return(&pb.Certificate{ PemCertificate: testIntermediateCertificate, PemCertificateChain: []string{testRootCertificate}, }, nil) - m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().ActivateCertificateAuthority(anee, anee).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "ActivateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1105,12 +1108,12 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().EnableCertificateAuthority(any, any).Return(nil, errTest) + m.EXPECT().EnableCertificateAuthority(anee, anee).Return(nil, errTest) // fail EnableCertificateAuthority.Wait intermediate - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1119,15 +1122,15 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: testIntermediateCsr, }, nil) - m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{ + m.EXPECT().CreateCertificate(anee, anee).Return(&pb.Certificate{ PemCertificate: testIntermediateCertificate, PemCertificateChain: []string{testRootCertificate}, }, nil) - m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().ActivateCertificateAuthority(anee, anee).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "ActivateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1137,13 +1140,13 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().EnableCertificateAuthority(any, any).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) - mos.EXPECT().GetOperation(any, any).Return(nil, errTest) + m.EXPECT().EnableCertificateAuthority(anee, anee).Return(fake.EnableCertificateAuthorityOperation("EnableCertificateAuthorityOperation"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(nil, errTest) // fail FetchCertificateAuthorityCsr - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1152,12 +1155,12 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(nil, errTest) + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(nil, errTest) // fail CreateCertificate - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1166,15 +1169,15 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: testIntermediateCsr, }, nil) - m.EXPECT().CreateCertificate(any, any).Return(nil, errTest) + m.EXPECT().CreateCertificate(anee, anee).Return(nil, errTest) // fail ActivateCertificateAuthority - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1183,19 +1186,19 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: testIntermediateCsr, }, nil) - m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{ + m.EXPECT().CreateCertificate(anee, anee).Return(&pb.Certificate{ PemCertificate: testIntermediateCertificate, PemCertificateChain: []string{testRootCertificate}, }, nil) - m.EXPECT().ActivateCertificateAuthority(any, any).Return(nil, errTest) + m.EXPECT().ActivateCertificateAuthority(anee, anee).Return(nil, errTest) // fail ActivateCertificateAuthority.Wait - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1204,20 +1207,20 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: testIntermediateCsr, }, nil) - m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{ + m.EXPECT().CreateCertificate(anee, anee).Return(&pb.Certificate{ PemCertificate: testIntermediateCertificate, PemCertificateChain: []string{testRootCertificate}, }, nil) - m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(nil, errTest) + m.EXPECT().ActivateCertificateAuthority(anee, anee).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(nil, errTest) // fail x509util.CreateCertificate - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1226,14 +1229,14 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: testIntermediateCsr, }, nil) // fail parseCertificateRequest - m.EXPECT().GetCaPool(any, any).Return(&pb.CaPool{Name: testCaPoolName}, nil) - m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) - mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + m.EXPECT().GetCaPool(anee, anee).Return(&pb.CaPool{Name: testCaPoolName}, nil) + m.EXPECT().CreateCertificateAuthority(anee, anee).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(anee, anee).Return(&longrunningpb.Operation{ Name: "CreateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ @@ -1242,7 +1245,7 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { })).(*anypb.Any), }, }, nil) - m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + m.EXPECT().FetchCertificateAuthorityCsr(anee, anee).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: "Not a CSR", }, nil) diff --git a/cas/cloudcas/mock_client_test.go b/cas/cloudcas/mock_client_test.go index 90d1a2f9..aaf6b3b5 100644 --- a/cas/cloudcas/mock_client_test.go +++ b/cas/cloudcas/mock_client_test.go @@ -9,36 +9,36 @@ import ( reflect "reflect" privateca "cloud.google.com/go/security/privateca/apiv1" + privatecapb "cloud.google.com/go/security/privateca/apiv1/privatecapb" gomock "github.com/golang/mock/gomock" gax "github.com/googleapis/gax-go/v2" - privateca0 "google.golang.org/genproto/googleapis/cloud/security/privateca/v1" ) -// MockCertificateAuthorityClient is a mock of CertificateAuthorityClient interface +// MockCertificateAuthorityClient is a mock of CertificateAuthorityClient interface. type MockCertificateAuthorityClient struct { ctrl *gomock.Controller recorder *MockCertificateAuthorityClientMockRecorder } -// MockCertificateAuthorityClientMockRecorder is the mock recorder for MockCertificateAuthorityClient +// MockCertificateAuthorityClientMockRecorder is the mock recorder for MockCertificateAuthorityClient. type MockCertificateAuthorityClientMockRecorder struct { mock *MockCertificateAuthorityClient } -// NewMockCertificateAuthorityClient creates a new mock instance +// NewMockCertificateAuthorityClient creates a new mock instance. func NewMockCertificateAuthorityClient(ctrl *gomock.Controller) *MockCertificateAuthorityClient { mock := &MockCertificateAuthorityClient{ctrl: ctrl} mock.recorder = &MockCertificateAuthorityClientMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockCertificateAuthorityClient) EXPECT() *MockCertificateAuthorityClientMockRecorder { return m.recorder } -// ActivateCertificateAuthority mocks base method -func (m *MockCertificateAuthorityClient) ActivateCertificateAuthority(arg0 context.Context, arg1 *privateca0.ActivateCertificateAuthorityRequest, arg2 ...gax.CallOption) (*privateca.ActivateCertificateAuthorityOperation, error) { +// ActivateCertificateAuthority mocks base method. +func (m *MockCertificateAuthorityClient) ActivateCertificateAuthority(arg0 context.Context, arg1 *privatecapb.ActivateCertificateAuthorityRequest, arg2 ...gax.CallOption) (*privateca.ActivateCertificateAuthorityOperation, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { @@ -50,15 +50,15 @@ func (m *MockCertificateAuthorityClient) ActivateCertificateAuthority(arg0 conte return ret0, ret1 } -// ActivateCertificateAuthority indicates an expected call of ActivateCertificateAuthority +// ActivateCertificateAuthority indicates an expected call of ActivateCertificateAuthority. func (mr *MockCertificateAuthorityClientMockRecorder) ActivateCertificateAuthority(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActivateCertificateAuthority", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).ActivateCertificateAuthority), varargs...) } -// CreateCaPool mocks base method -func (m *MockCertificateAuthorityClient) CreateCaPool(arg0 context.Context, arg1 *privateca0.CreateCaPoolRequest, arg2 ...gax.CallOption) (*privateca.CreateCaPoolOperation, error) { +// CreateCaPool mocks base method. +func (m *MockCertificateAuthorityClient) CreateCaPool(arg0 context.Context, arg1 *privatecapb.CreateCaPoolRequest, arg2 ...gax.CallOption) (*privateca.CreateCaPoolOperation, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { @@ -70,35 +70,35 @@ func (m *MockCertificateAuthorityClient) CreateCaPool(arg0 context.Context, arg1 return ret0, ret1 } -// CreateCaPool indicates an expected call of CreateCaPool +// CreateCaPool indicates an expected call of CreateCaPool. func (mr *MockCertificateAuthorityClientMockRecorder) CreateCaPool(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCaPool", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).CreateCaPool), varargs...) } -// CreateCertificate mocks base method -func (m *MockCertificateAuthorityClient) CreateCertificate(arg0 context.Context, arg1 *privateca0.CreateCertificateRequest, arg2 ...gax.CallOption) (*privateca0.Certificate, error) { +// CreateCertificate mocks base method. +func (m *MockCertificateAuthorityClient) CreateCertificate(arg0 context.Context, arg1 *privatecapb.CreateCertificateRequest, arg2 ...gax.CallOption) (*privatecapb.Certificate, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "CreateCertificate", varargs...) - ret0, _ := ret[0].(*privateca0.Certificate) + ret0, _ := ret[0].(*privatecapb.Certificate) ret1, _ := ret[1].(error) return ret0, ret1 } -// CreateCertificate indicates an expected call of CreateCertificate +// CreateCertificate indicates an expected call of CreateCertificate. func (mr *MockCertificateAuthorityClientMockRecorder) CreateCertificate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCertificate", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).CreateCertificate), varargs...) } -// CreateCertificateAuthority mocks base method -func (m *MockCertificateAuthorityClient) CreateCertificateAuthority(arg0 context.Context, arg1 *privateca0.CreateCertificateAuthorityRequest, arg2 ...gax.CallOption) (*privateca.CreateCertificateAuthorityOperation, error) { +// CreateCertificateAuthority mocks base method. +func (m *MockCertificateAuthorityClient) CreateCertificateAuthority(arg0 context.Context, arg1 *privatecapb.CreateCertificateAuthorityRequest, arg2 ...gax.CallOption) (*privateca.CreateCertificateAuthorityOperation, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { @@ -110,15 +110,15 @@ func (m *MockCertificateAuthorityClient) CreateCertificateAuthority(arg0 context return ret0, ret1 } -// CreateCertificateAuthority indicates an expected call of CreateCertificateAuthority +// CreateCertificateAuthority indicates an expected call of CreateCertificateAuthority. func (mr *MockCertificateAuthorityClientMockRecorder) CreateCertificateAuthority(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCertificateAuthority", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).CreateCertificateAuthority), varargs...) } -// EnableCertificateAuthority mocks base method -func (m *MockCertificateAuthorityClient) EnableCertificateAuthority(arg0 context.Context, arg1 *privateca0.EnableCertificateAuthorityRequest, arg2 ...gax.CallOption) (*privateca.EnableCertificateAuthorityOperation, error) { +// EnableCertificateAuthority mocks base method. +func (m *MockCertificateAuthorityClient) EnableCertificateAuthority(arg0 context.Context, arg1 *privatecapb.EnableCertificateAuthorityRequest, arg2 ...gax.CallOption) (*privateca.EnableCertificateAuthorityOperation, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { @@ -130,87 +130,87 @@ func (m *MockCertificateAuthorityClient) EnableCertificateAuthority(arg0 context return ret0, ret1 } -// EnableCertificateAuthority indicates an expected call of EnableCertificateAuthority +// EnableCertificateAuthority indicates an expected call of EnableCertificateAuthority. func (mr *MockCertificateAuthorityClientMockRecorder) EnableCertificateAuthority(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableCertificateAuthority", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).EnableCertificateAuthority), varargs...) } -// FetchCertificateAuthorityCsr mocks base method -func (m *MockCertificateAuthorityClient) FetchCertificateAuthorityCsr(arg0 context.Context, arg1 *privateca0.FetchCertificateAuthorityCsrRequest, arg2 ...gax.CallOption) (*privateca0.FetchCertificateAuthorityCsrResponse, error) { +// FetchCertificateAuthorityCsr mocks base method. +func (m *MockCertificateAuthorityClient) FetchCertificateAuthorityCsr(arg0 context.Context, arg1 *privatecapb.FetchCertificateAuthorityCsrRequest, arg2 ...gax.CallOption) (*privatecapb.FetchCertificateAuthorityCsrResponse, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "FetchCertificateAuthorityCsr", varargs...) - ret0, _ := ret[0].(*privateca0.FetchCertificateAuthorityCsrResponse) + ret0, _ := ret[0].(*privatecapb.FetchCertificateAuthorityCsrResponse) ret1, _ := ret[1].(error) return ret0, ret1 } -// FetchCertificateAuthorityCsr indicates an expected call of FetchCertificateAuthorityCsr +// FetchCertificateAuthorityCsr indicates an expected call of FetchCertificateAuthorityCsr. func (mr *MockCertificateAuthorityClientMockRecorder) FetchCertificateAuthorityCsr(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchCertificateAuthorityCsr", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).FetchCertificateAuthorityCsr), varargs...) } -// GetCaPool mocks base method -func (m *MockCertificateAuthorityClient) GetCaPool(arg0 context.Context, arg1 *privateca0.GetCaPoolRequest, arg2 ...gax.CallOption) (*privateca0.CaPool, error) { +// GetCaPool mocks base method. +func (m *MockCertificateAuthorityClient) GetCaPool(arg0 context.Context, arg1 *privatecapb.GetCaPoolRequest, arg2 ...gax.CallOption) (*privatecapb.CaPool, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetCaPool", varargs...) - ret0, _ := ret[0].(*privateca0.CaPool) + ret0, _ := ret[0].(*privatecapb.CaPool) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetCaPool indicates an expected call of GetCaPool +// GetCaPool indicates an expected call of GetCaPool. func (mr *MockCertificateAuthorityClientMockRecorder) GetCaPool(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCaPool", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).GetCaPool), varargs...) } -// GetCertificateAuthority mocks base method -func (m *MockCertificateAuthorityClient) GetCertificateAuthority(arg0 context.Context, arg1 *privateca0.GetCertificateAuthorityRequest, arg2 ...gax.CallOption) (*privateca0.CertificateAuthority, error) { +// GetCertificateAuthority mocks base method. +func (m *MockCertificateAuthorityClient) GetCertificateAuthority(arg0 context.Context, arg1 *privatecapb.GetCertificateAuthorityRequest, arg2 ...gax.CallOption) (*privatecapb.CertificateAuthority, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetCertificateAuthority", varargs...) - ret0, _ := ret[0].(*privateca0.CertificateAuthority) + ret0, _ := ret[0].(*privatecapb.CertificateAuthority) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetCertificateAuthority indicates an expected call of GetCertificateAuthority +// GetCertificateAuthority indicates an expected call of GetCertificateAuthority. func (mr *MockCertificateAuthorityClientMockRecorder) GetCertificateAuthority(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCertificateAuthority", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).GetCertificateAuthority), varargs...) } -// RevokeCertificate mocks base method -func (m *MockCertificateAuthorityClient) RevokeCertificate(arg0 context.Context, arg1 *privateca0.RevokeCertificateRequest, arg2 ...gax.CallOption) (*privateca0.Certificate, error) { +// RevokeCertificate mocks base method. +func (m *MockCertificateAuthorityClient) RevokeCertificate(arg0 context.Context, arg1 *privatecapb.RevokeCertificateRequest, arg2 ...gax.CallOption) (*privatecapb.Certificate, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0, arg1} for _, a := range arg2 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "RevokeCertificate", varargs...) - ret0, _ := ret[0].(*privateca0.Certificate) + ret0, _ := ret[0].(*privatecapb.Certificate) ret1, _ := ret[1].(error) return ret0, ret1 } -// RevokeCertificate indicates an expected call of RevokeCertificate +// RevokeCertificate indicates an expected call of RevokeCertificate. func (mr *MockCertificateAuthorityClientMockRecorder) RevokeCertificate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0, arg1}, arg2...) diff --git a/cas/cloudcas/mock_operation_server_test.go b/cas/cloudcas/mock_operation_server_test.go index 43dfa713..157b85bb 100644 --- a/cas/cloudcas/mock_operation_server_test.go +++ b/cas/cloudcas/mock_operation_server_test.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: google.golang.org/genproto/googleapis/longrunning (interfaces: OperationsServer) +// Source: cloud.google.com/go/longrunning/autogen/longrunningpb (interfaces: OperationsServer) // Package cloudcas is a generated GoMock package. package cloudcas @@ -8,36 +8,36 @@ import ( context "context" reflect "reflect" + longrunningpb "cloud.google.com/go/longrunning/autogen/longrunningpb" gomock "github.com/golang/mock/gomock" - longrunning "google.golang.org/genproto/googleapis/longrunning" emptypb "google.golang.org/protobuf/types/known/emptypb" ) -// MockOperationsServer is a mock of OperationsServer interface +// MockOperationsServer is a mock of OperationsServer interface. type MockOperationsServer struct { ctrl *gomock.Controller recorder *MockOperationsServerMockRecorder } -// MockOperationsServerMockRecorder is the mock recorder for MockOperationsServer +// MockOperationsServerMockRecorder is the mock recorder for MockOperationsServer. type MockOperationsServerMockRecorder struct { mock *MockOperationsServer } -// NewMockOperationsServer creates a new mock instance +// NewMockOperationsServer creates a new mock instance. func NewMockOperationsServer(ctrl *gomock.Controller) *MockOperationsServer { mock := &MockOperationsServer{ctrl: ctrl} mock.recorder = &MockOperationsServerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockOperationsServer) EXPECT() *MockOperationsServerMockRecorder { return m.recorder } -// CancelOperation mocks base method -func (m *MockOperationsServer) CancelOperation(arg0 context.Context, arg1 *longrunning.CancelOperationRequest) (*emptypb.Empty, error) { +// CancelOperation mocks base method. +func (m *MockOperationsServer) CancelOperation(arg0 context.Context, arg1 *longrunningpb.CancelOperationRequest) (*emptypb.Empty, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CancelOperation", arg0, arg1) ret0, _ := ret[0].(*emptypb.Empty) @@ -45,14 +45,14 @@ func (m *MockOperationsServer) CancelOperation(arg0 context.Context, arg1 *longr return ret0, ret1 } -// CancelOperation indicates an expected call of CancelOperation +// CancelOperation indicates an expected call of CancelOperation. func (mr *MockOperationsServerMockRecorder) CancelOperation(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelOperation", reflect.TypeOf((*MockOperationsServer)(nil).CancelOperation), arg0, arg1) } -// DeleteOperation mocks base method -func (m *MockOperationsServer) DeleteOperation(arg0 context.Context, arg1 *longrunning.DeleteOperationRequest) (*emptypb.Empty, error) { +// DeleteOperation mocks base method. +func (m *MockOperationsServer) DeleteOperation(arg0 context.Context, arg1 *longrunningpb.DeleteOperationRequest) (*emptypb.Empty, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeleteOperation", arg0, arg1) ret0, _ := ret[0].(*emptypb.Empty) @@ -60,52 +60,52 @@ func (m *MockOperationsServer) DeleteOperation(arg0 context.Context, arg1 *longr return ret0, ret1 } -// DeleteOperation indicates an expected call of DeleteOperation +// DeleteOperation indicates an expected call of DeleteOperation. func (mr *MockOperationsServerMockRecorder) DeleteOperation(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOperation", reflect.TypeOf((*MockOperationsServer)(nil).DeleteOperation), arg0, arg1) } -// GetOperation mocks base method -func (m *MockOperationsServer) GetOperation(arg0 context.Context, arg1 *longrunning.GetOperationRequest) (*longrunning.Operation, error) { +// GetOperation mocks base method. +func (m *MockOperationsServer) GetOperation(arg0 context.Context, arg1 *longrunningpb.GetOperationRequest) (*longrunningpb.Operation, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetOperation", arg0, arg1) - ret0, _ := ret[0].(*longrunning.Operation) + ret0, _ := ret[0].(*longrunningpb.Operation) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetOperation indicates an expected call of GetOperation +// GetOperation indicates an expected call of GetOperation. func (mr *MockOperationsServerMockRecorder) GetOperation(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOperation", reflect.TypeOf((*MockOperationsServer)(nil).GetOperation), arg0, arg1) } -// ListOperations mocks base method -func (m *MockOperationsServer) ListOperations(arg0 context.Context, arg1 *longrunning.ListOperationsRequest) (*longrunning.ListOperationsResponse, error) { +// ListOperations mocks base method. +func (m *MockOperationsServer) ListOperations(arg0 context.Context, arg1 *longrunningpb.ListOperationsRequest) (*longrunningpb.ListOperationsResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListOperations", arg0, arg1) - ret0, _ := ret[0].(*longrunning.ListOperationsResponse) + ret0, _ := ret[0].(*longrunningpb.ListOperationsResponse) ret1, _ := ret[1].(error) return ret0, ret1 } -// ListOperations indicates an expected call of ListOperations +// ListOperations indicates an expected call of ListOperations. func (mr *MockOperationsServerMockRecorder) ListOperations(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListOperations", reflect.TypeOf((*MockOperationsServer)(nil).ListOperations), arg0, arg1) } -// WaitOperation mocks base method -func (m *MockOperationsServer) WaitOperation(arg0 context.Context, arg1 *longrunning.WaitOperationRequest) (*longrunning.Operation, error) { +// WaitOperation mocks base method. +func (m *MockOperationsServer) WaitOperation(arg0 context.Context, arg1 *longrunningpb.WaitOperationRequest) (*longrunningpb.Operation, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "WaitOperation", arg0, arg1) - ret0, _ := ret[0].(*longrunning.Operation) + ret0, _ := ret[0].(*longrunningpb.Operation) ret1, _ := ret[1].(error) return ret0, ret1 } -// WaitOperation indicates an expected call of WaitOperation +// WaitOperation indicates an expected call of WaitOperation. func (mr *MockOperationsServerMockRecorder) WaitOperation(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitOperation", reflect.TypeOf((*MockOperationsServer)(nil).WaitOperation), arg0, arg1) diff --git a/cas/softcas/softcas.go b/cas/softcas/softcas.go index dc6343f6..6eae9e9e 100644 --- a/cas/softcas/softcas.go +++ b/cas/softcas/softcas.go @@ -3,6 +3,7 @@ package softcas import ( "context" "crypto" + "crypto/rand" "crypto/rsa" "crypto/x509" "time" @@ -132,6 +133,20 @@ func (c *SoftCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1 }, nil } +// CreateCRL will create a new CRL based on the RevocationList passed to it +func (c *SoftCAS) CreateCRL(req *apiv1.CreateCRLRequest) (*apiv1.CreateCRLResponse, error) { + certChain, signer, err := c.getCertSigner() + if err != nil { + return nil, err + } + revocationListBytes, err := x509.CreateRevocationList(rand.Reader, req.RevocationList, certChain[0], signer) + if err != nil { + return nil, err + } + + return &apiv1.CreateCRLResponse{CRL: revocationListBytes}, nil +} + // CreateCertificateAuthority creates a root or an intermediate certificate. func (c *SoftCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthorityRequest) (*apiv1.CreateCertificateAuthorityResponse, error) { switch { @@ -215,7 +230,6 @@ func (c *SoftCAS) getCertSigner() ([]*x509.Certificate, crypto.Signer, error) { return c.CertificateSigner() } return c.CertificateChain, c.Signer, nil - } // createKey uses the configured kms to create a key. diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 8867b9b4..5c8a2f1f 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -261,9 +261,6 @@ func TestSoftCAS_CreateCertificate(t *testing.T) { tmplNotBefore := *testTemplate tmplNotBefore.NotBefore = testNow - tmplNotAfter := *testTemplate - tmplNotAfter.NotAfter = testNow.Add(24 * time.Hour) - tmplWithLifetime := *testTemplate tmplWithLifetime.NotBefore = testNow tmplWithLifetime.NotAfter = testNow.Add(24 * time.Hour) diff --git a/cas/stepcas/issuer_test.go b/cas/stepcas/issuer_test.go index c968237a..7d468e38 100644 --- a/cas/stepcas/issuer_test.go +++ b/cas/stepcas/issuer_test.go @@ -15,11 +15,11 @@ import ( type mockErrIssuer struct{} func (m mockErrIssuer) SignToken(subject string, sans []string, info *raInfo) (string, error) { - return "", apiv1.ErrNotImplemented{} + return "", apiv1.NotImplementedError{} } func (m mockErrIssuer) RevokeToken(subject string) (string, error) { - return "", apiv1.ErrNotImplemented{} + return "", apiv1.NotImplementedError{} } func (m mockErrIssuer) Lifetime(d time.Duration) time.Duration { @@ -29,7 +29,7 @@ func (m mockErrIssuer) Lifetime(d time.Duration) time.Duration { type mockErrSigner struct{} func (s *mockErrSigner) Sign(payload []byte) (*jose.JSONWebSignature, error) { - return nil, apiv1.ErrNotImplemented{} + return nil, apiv1.NotImplementedError{} } func (s *mockErrSigner) Options() jose.SignerOptions { diff --git a/cas/stepcas/stepcas.go b/cas/stepcas/stepcas.go index f8770923..c64963e6 100644 --- a/cas/stepcas/stepcas.go +++ b/cas/stepcas/stepcas.go @@ -101,7 +101,25 @@ func (s *StepCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1 // RenewCertificate will always return a non-implemented error as mTLS renewals // are not supported yet. func (s *StepCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { - return nil, apiv1.ErrNotImplemented{Message: "stepCAS does not support mTLS renewals"} + if req.Token == "" { + return nil, apiv1.ValidationError{Message: "renewCertificateRequest `token` cannot be empty"} + } + + resp, err := s.client.RenewWithToken(req.Token) + if err != nil { + return nil, err + } + + var chain []*x509.Certificate + cert := resp.CertChainPEM[0].Certificate + for _, c := range resp.CertChainPEM[1:] { + chain = append(chain, c.Certificate) + } + + return &apiv1.RenewCertificateResponse{ + Certificate: cert, + CertificateChain: chain, + }, nil } // RevokeCertificate revokes a certificate. diff --git a/cas/stepcas/stepcas_test.go b/cas/stepcas/stepcas_test.go index cc8ea72e..6691a4b4 100644 --- a/cas/stepcas/stepcas_test.go +++ b/cas/stepcas/stepcas_test.go @@ -147,6 +147,16 @@ func testCAHelper(t *testing.T) (*url.URL, *ca.Client) { writeJSON(w, api.SignResponse{ CertChainPEM: []api.Certificate{api.NewCertificate(testCrt), api.NewCertificate(testIssCrt)}, }) + case r.RequestURI == "/renew": + if r.Header.Get("Authorization") == "Bearer fail" { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, `{"error":"fail","message":"fail"}`) + return + } + w.WriteHeader(http.StatusOK) + writeJSON(w, api.SignResponse{ + CertChainPEM: []api.Certificate{api.NewCertificate(testCrt), api.NewCertificate(testIssCrt)}, + }) case r.RequestURI == "/revoke": var msg api.RevokeRequest parseJSON(r, &msg) @@ -723,9 +733,14 @@ func TestStepCAS_CreateCertificate(t *testing.T) { func TestStepCAS_RenewCertificate(t *testing.T) { caURL, client := testCAHelper(t) - x5c := testX5CIssuer(t, caURL, "") jwk := testJWKIssuer(t, caURL, "") + tokenIssuer := testX5CIssuer(t, caURL, "") + token, err := tokenIssuer.SignToken("test", []string{"test.example.com"}, nil) + if err != nil { + t.Fatal(err) + } + type fields struct { iss stepIssuer client *ca.Client @@ -741,13 +756,25 @@ func TestStepCAS_RenewCertificate(t *testing.T) { want *apiv1.RenewCertificateResponse wantErr bool }{ - {"not implemented", fields{x5c, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ - CSR: testCR, + {"ok", fields{jwk, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ + Template: &x509.Certificate{}, + Backdate: time.Minute, + Lifetime: time.Hour, + Token: token, + }}, &apiv1.RenewCertificateResponse{ + Certificate: testCrt, + CertificateChain: []*x509.Certificate{testIssCrt}, + }, false}, + {"fail no token", fields{jwk, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ + Template: &x509.Certificate{}, + Backdate: time.Minute, Lifetime: time.Hour, }}, nil, true}, - {"not implemented jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ - CSR: testCR, + {"fail bad token", fields{jwk, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{ + Template: &x509.Certificate{}, + Backdate: time.Minute, Lifetime: time.Hour, + Token: "fail", }}, nil, true}, } for _, tt := range tests { @@ -763,7 +790,10 @@ func TestStepCAS_RenewCertificate(t *testing.T) { return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("StepCAS.RenewCertificate() = %v, want %v", got, tt.want) + t.Error(reflect.DeepEqual(got.Certificate, tt.want.Certificate)) + t.Error(reflect.DeepEqual(got.CertificateChain, tt.want.CertificateChain)) + + t.Errorf("StepCAS.RenewCertificate() = %v, want %v", got.Certificate.Subject, tt.want.Certificate.Subject) } }) } diff --git a/cas/vaultcas/vaultcas.go b/cas/vaultcas/vaultcas.go index a5658620..c618a0a0 100644 --- a/cas/vaultcas/vaultcas.go +++ b/cas/vaultcas/vaultcas.go @@ -162,7 +162,7 @@ func (v *VaultCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityReq // RenewCertificate will always return a non-implemented error as renewals // are not supported yet. func (v *VaultCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { - return nil, apiv1.ErrNotImplemented{Message: "vaultCAS does not support renewals"} + return nil, apiv1.NotImplementedError{Message: "vaultCAS does not support renewals"} } // RevokeCertificate revokes a certificate by serial number. diff --git a/cmd/step-awskms-init/main.go b/cmd/step-awskms-init/main.go index ee46ba94..81a91067 100644 --- a/cmd/step-awskms-init/main.go +++ b/cmd/step-awskms-init/main.go @@ -4,7 +4,7 @@ import ( "context" "crypto" "crypto/rand" - "crypto/sha1" // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + "crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -242,7 +242,7 @@ func mustSubjectKeyID(key crypto.PublicKey) []byte { if err != nil { panic(err) } - // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 hash := sha1.Sum(b) return hash[:] } diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index d070b6cf..11756b93 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -14,7 +14,7 @@ import ( "time" // Server profiler - // nolint:gosec // profile server, if enabled runs on a different port + //nolint:gosec // profile server, if enabled runs on a different port _ "net/http/pprof" "github.com/smallstep/certificates/authority" @@ -25,6 +25,7 @@ import ( "go.step.sm/cli-utils/step" "go.step.sm/cli-utils/ui" "go.step.sm/cli-utils/usage" + "go.step.sm/crypto/pemutil" // Enabled kms interfaces. _ "go.step.sm/crypto/kms/awskms" @@ -52,6 +53,10 @@ func init() { step.Set("Smallstep CA", Version, BuildTime) authority.GlobalVersion.Version = Version rand.Seed(time.Now().UnixNano()) + // Add support for asking passwords + pemutil.PromptPassword = func(msg string) ([]byte, error) { + return ui.PromptPassword(msg) + } } func exit(code int) { @@ -176,7 +181,11 @@ $ step-ca --context=mybiz --password-file ./password.txt debugProfAddr := os.Getenv("STEP_PROF_ADDR") if debugProfAddr != "" { go func() { - log.Println(http.ListenAndServe(debugProfAddr, nil)) + srv := http.Server{ + Addr: debugProfAddr, + ReadHeaderTimeout: 15 * time.Second, + } + log.Println(srv.ListenAndServe()) }() } diff --git a/cmd/step-cloudkms-init/main.go b/cmd/step-cloudkms-init/main.go index 98d81ac0..6cc36adf 100644 --- a/cmd/step-cloudkms-init/main.go +++ b/cmd/step-cloudkms-init/main.go @@ -4,7 +4,7 @@ import ( "context" "crypto" "crypto/rand" - "crypto/sha1" // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + "crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -280,7 +280,7 @@ func mustSubjectKeyID(key crypto.PublicKey) []byte { if err != nil { panic(err) } - // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 hash := sha1.Sum(b) return hash[:] } diff --git a/cmd/step-pkcs11-init/main.go b/cmd/step-pkcs11-init/main.go index 7595000c..30258cdd 100644 --- a/cmd/step-pkcs11-init/main.go +++ b/cmd/step-pkcs11-init/main.go @@ -6,7 +6,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" - "crypto/sha1" // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + "crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -547,7 +547,7 @@ func mustSubjectKeyID(key crypto.PublicKey) []byte { if err != nil { panic(err) } - // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 hash := sha1.Sum(b) return hash[:] } diff --git a/cmd/step-yubikey-init/main.go b/cmd/step-yubikey-init/main.go index a06afe04..cd6018cf 100644 --- a/cmd/step-yubikey-init/main.go +++ b/cmd/step-yubikey-init/main.go @@ -6,7 +6,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" - "crypto/sha1" // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + "crypto/sha1" //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 "crypto/x509" "crypto/x509/pkix" "encoding/hex" @@ -349,7 +349,7 @@ func mustSubjectKeyID(key crypto.PublicKey) []byte { if err != nil { panic(err) } - // nolint:gosec // used to create the Subject Key Identifier by RFC 5280 + //nolint:gosec // used to create the Subject Key Identifier by RFC 5280 hash := sha1.Sum(b) return hash[:] } diff --git a/commands/app.go b/commands/app.go index 7545f1df..66030be2 100644 --- a/commands/app.go +++ b/commands/app.go @@ -12,6 +12,7 @@ import ( "unicode" "github.com/pkg/errors" + "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/ca" @@ -68,9 +69,23 @@ certificate issuer private key used in the RA mode.`, }, cli.StringFlag{ Name: "context", - Usage: "The name of the authority's context.", + Usage: "the of the authority's context.", EnvVar: "STEP_CA_CONTEXT", }, + cli.IntFlag{ + Name: "acme-http-port", + Usage: `the used on http-01 challenges. It can be changed for testing purposes. +Requires **--insecure** flag.`, + }, + cli.IntFlag{ + Name: "acme-tls-port", + Usage: `the used on tls-alpn-01 challenges. It can be changed for testing purposes. +Requires **--insecure** flag.`, + }, + cli.BoolFlag{ + Name: "insecure", + Usage: "enable insecure flags.", + }, }, } @@ -88,6 +103,23 @@ func appAction(ctx *cli.Context) error { return errs.TooManyArguments(ctx) } + // Allow custom ACME ports with insecure + if acmePort := ctx.Int("acme-http-port"); acmePort != 0 { + if ctx.Bool("insecure") { + acme.InsecurePortHTTP01 = acmePort + } else { + return fmt.Errorf("flag '--acme-http-port' requires the '--insecure' flag") + } + } + if acmePort := ctx.Int("acme-tls-port"); acmePort != 0 { + if ctx.Bool("insecure") { + acme.InsecurePortTLSALPN01 = acmePort + } else { + return fmt.Errorf("flag '--acme-tls-port' requires the '--insecure' flag") + } + } + + // Allow custom contexts. if caCtx := ctx.String("context"); caCtx != "" { if err := step.Contexts().SetCurrent(caCtx); err != nil { return err @@ -196,7 +228,7 @@ To get a linked authority token: } go ca.StopReloaderHandler(srv) - if err = srv.Run(); err != nil && err != http.ErrServerClosed { + if err = srv.Run(); err != nil && !errors.Is(err, http.ErrServerClosed) { fatal(err) } return nil diff --git a/commands/onboard.go b/commands/onboard.go index bb704fd4..ef3b7854 100644 --- a/commands/onboard.go +++ b/commands/onboard.go @@ -92,11 +92,12 @@ func onboardAction(ctx *cli.Context) error { token := ctx.Args().Get(0) onboardingURL := u.ResolveReference(&url.URL{Path: token}).String() - // nolint:gosec // onboarding url + //nolint:gosec // onboarding url res, err := http.Get(onboardingURL) if err != nil { return errors.Wrap(err, "error connecting onboarding guide") } + defer res.Body.Close() if res.StatusCode >= 400 { var msg onboardingError if err := readJSON(res.Body, &msg); err != nil { @@ -133,7 +134,7 @@ func onboardAction(ctx *cli.Context) error { return errors.Wrap(err, "error marshaling payload") } - // nolint:gosec // onboarding url + //nolint:gosec // onboarding url resp, err := http.Post(onboardingURL, "application/json", bytes.NewBuffer(payload)) if err != nil { return errors.Wrap(err, "error connecting onboarding guide") @@ -158,7 +159,7 @@ func onboardAction(ctx *cli.Context) error { } go ca.StopReloaderHandler(srv) - if err := srv.Run(); err != nil && err != http.ErrServerClosed { + if err := srv.Run(); err != nil && !errors.Is(err, http.ErrServerClosed) { fatal(err) } diff --git a/db/db.go b/db/db.go index 05f10793..b3137a50 100644 --- a/db/db.go +++ b/db/db.go @@ -19,6 +19,7 @@ var ( certsTable = []byte("x509_certs") certsDataTable = []byte("x509_certs_data") revokedCertsTable = []byte("revoked_x509_certs") + crlTable = []byte("x509_crl") revokedSSHCertsTable = []byte("revoked_ssh_certs") usedOTTTable = []byte("used_ott") sshCertsTable = []byte("ssh_certs") @@ -27,6 +28,10 @@ var ( sshHostPrincipalsTable = []byte("ssh_host_principals") ) +// TODO: at the moment we store a single CRL in the database, in a dedicated table. +// is this acceptable? probably not.... +var crlKey = []byte("crl") + // ErrAlreadyExists can be returned if the DB attempts to set a key that has // been previously set. var ErrAlreadyExists = errors.New("already exists") @@ -87,6 +92,13 @@ type CertificateStorer interface { StoreSSHCertificate(crt *ssh.Certificate) error } +// CertificateRevocationListDB is an interface to indicate whether the DB supports CRL generation +type CertificateRevocationListDB interface { + GetRevokedCertificates() (*[]RevokedCertificateInfo, error) + GetCRL() (*CertificateRevocationListInfo, error) + StoreCRL(*CertificateRevocationListInfo) error +} + // DB is a wrapper over the nosql.DB interface. type DB struct { nosql.DB @@ -113,7 +125,7 @@ func New(c *Config) (AuthDB, error) { tables := [][]byte{ revokedCertsTable, certsTable, usedOTTTable, sshCertsTable, sshHostsTable, sshHostPrincipalsTable, sshUsersTable, - revokedSSHCertsTable, certsDataTable, + revokedSSHCertsTable, certsDataTable, crlTable, } for _, b := range tables { if err := db.CreateTable(b); err != nil { @@ -133,11 +145,21 @@ type RevokedCertificateInfo struct { ReasonCode int Reason string RevokedAt time.Time + ExpiresAt time.Time TokenID string MTLS bool ACME bool } +// CertificateRevocationListInfo contains a CRL in DER format and associated +// metadata to allow a decision on whether to regenerate the CRL or not easier +type CertificateRevocationListInfo struct { + Number int64 + ExpiresAt time.Time + Duration time.Duration + DER []byte +} + // IsRevoked returns whether or not a certificate with the given identifier // has been revoked. // In the case of an X509 Certificate the `id` should be the Serial Number of @@ -220,6 +242,51 @@ func (db *DB) RevokeSSH(rci *RevokedCertificateInfo) error { } } +// GetRevokedCertificates gets a list of all revoked certificates. +func (db *DB) GetRevokedCertificates() (*[]RevokedCertificateInfo, error) { + entries, err := db.List(revokedCertsTable) + if err != nil { + return nil, err + } + var revokedCerts []RevokedCertificateInfo + for _, e := range entries { + var data RevokedCertificateInfo + if err := json.Unmarshal(e.Value, &data); err != nil { + return nil, err + } + revokedCerts = append(revokedCerts, data) + } + return &revokedCerts, nil +} + +// StoreCRL stores a CRL in the DB +func (db *DB) StoreCRL(crlInfo *CertificateRevocationListInfo) error { + crlInfoBytes, err := json.Marshal(crlInfo) + if err != nil { + return errors.Wrap(err, "json Marshal error") + } + + if err := db.Set(crlTable, crlKey, crlInfoBytes); err != nil { + return errors.Wrap(err, "database Set error") + } + return nil +} + +// GetCRL gets the existing CRL from the database +func (db *DB) GetCRL() (*CertificateRevocationListInfo, error) { + crlInfoBytes, err := db.Get(crlTable, crlKey) + if err != nil { + return nil, errors.Wrap(err, "database Get error") + } + + var crlInfo CertificateRevocationListInfo + err = json.Unmarshal(crlInfoBytes, &crlInfo) + if err != nil { + return nil, errors.Wrap(err, "json Unmarshal error") + } + return &crlInfo, err +} + // GetCertificate retrieves a certificate by the serial number. func (db *DB) GetCertificate(serialNumber string) (*x509.Certificate, error) { asn1Data, err := db.Get(certsTable, []byte(serialNumber)) @@ -257,7 +324,8 @@ func (db *DB) StoreCertificate(crt *x509.Certificate) error { // CertificateData is the JSON representation of the data stored in // x509_certs_data table. type CertificateData struct { - Provisioner *ProvisionerData `json:"provisioner,omitempty"` + Provisioner *ProvisionerData `json:"provisioner,omitempty"` + RaInfo *provisioner.RAInfo `json:"ra,omitempty"` } // ProvisionerData is the JSON representation of the provisioner stored in the @@ -268,6 +336,10 @@ type ProvisionerData struct { Type string `json:"type"` } +type raProvisioner interface { + RAInfo() *provisioner.RAInfo +} + // StoreCertificateChain stores the leaf certificate and the provisioner that // authorized the certificate. func (db *DB) StoreCertificateChain(p provisioner.Interface, chain ...*x509.Certificate) error { @@ -280,6 +352,9 @@ func (db *DB) StoreCertificateChain(p provisioner.Interface, chain ...*x509.Cert Name: p.GetName(), Type: p.GetType().String(), } + if rap, ok := p.(raProvisioner); ok { + data.RaInfo = rap.RAInfo() + } } b, err := json.Marshal(data) if err != nil { @@ -295,6 +370,31 @@ func (db *DB) StoreCertificateChain(p provisioner.Interface, chain ...*x509.Cert return nil } +// StoreRenewedCertificate stores the leaf certificate and the provisioner that +// authorized the old certificate if available. +func (db *DB) StoreRenewedCertificate(oldCert *x509.Certificate, chain ...*x509.Certificate) error { + var certificateData []byte + if data, err := db.GetCertificateData(oldCert.SerialNumber.String()); err == nil { + if b, err := json.Marshal(data); err == nil { + certificateData = b + } + } + + leaf := chain[0] + serialNumber := []byte(leaf.SerialNumber.String()) + + // Add certificate and certificate data in one transaction. + tx := new(database.Tx) + tx.Set(certsTable, serialNumber, leaf.Raw) + if certificateData != nil { + tx.Set(certsDataTable, serialNumber, certificateData) + } + if err := db.Update(tx); err != nil { + return errors.Wrap(err, "database Update error") + } + return nil +} + // UseToken returns true if we were able to successfully store the token for // for the first time, false otherwise. func (db *DB) UseToken(id, tok string) (bool, error) { @@ -382,20 +482,44 @@ func (db *DB) Shutdown() error { // MockAuthDB mocks the AuthDB interface. // type MockAuthDB struct { - Err error - Ret1 interface{} - MIsRevoked func(string) (bool, error) - MIsSSHRevoked func(string) (bool, error) - MRevoke func(rci *RevokedCertificateInfo) error - MRevokeSSH func(rci *RevokedCertificateInfo) error - MGetCertificate func(serialNumber string) (*x509.Certificate, error) - MGetCertificateData func(serialNumber string) (*CertificateData, error) - MStoreCertificate func(crt *x509.Certificate) error - MUseToken func(id, tok string) (bool, error) - MIsSSHHost func(principal string) (bool, error) - MStoreSSHCertificate func(crt *ssh.Certificate) error - MGetSSHHostPrincipals func() ([]string, error) - MShutdown func() error + Err error + Ret1 interface{} + MIsRevoked func(string) (bool, error) + MIsSSHRevoked func(string) (bool, error) + MRevoke func(rci *RevokedCertificateInfo) error + MRevokeSSH func(rci *RevokedCertificateInfo) error + MGetCertificate func(serialNumber string) (*x509.Certificate, error) + MGetCertificateData func(serialNumber string) (*CertificateData, error) + MStoreCertificate func(crt *x509.Certificate) error + MUseToken func(id, tok string) (bool, error) + MIsSSHHost func(principal string) (bool, error) + MStoreSSHCertificate func(crt *ssh.Certificate) error + MGetSSHHostPrincipals func() ([]string, error) + MShutdown func() error + MGetRevokedCertificates func() (*[]RevokedCertificateInfo, error) + MGetCRL func() (*CertificateRevocationListInfo, error) + MStoreCRL func(*CertificateRevocationListInfo) error +} + +func (m *MockAuthDB) GetRevokedCertificates() (*[]RevokedCertificateInfo, error) { + if m.MGetRevokedCertificates != nil { + return m.MGetRevokedCertificates() + } + return m.Ret1.(*[]RevokedCertificateInfo), m.Err +} + +func (m *MockAuthDB) GetCRL() (*CertificateRevocationListInfo, error) { + if m.MGetCRL != nil { + return m.MGetCRL() + } + return m.Ret1.(*CertificateRevocationListInfo), m.Err +} + +func (m *MockAuthDB) StoreCRL(info *CertificateRevocationListInfo) error { + if m.MStoreCRL != nil { + return m.MStoreCRL(info) + } + return m.Err } // IsRevoked mock. diff --git a/db/db_test.go b/db/db_test.go index b4515a5b..7668ae58 100644 --- a/db/db_test.go +++ b/db/db_test.go @@ -1,6 +1,7 @@ package db import ( + "bytes" "crypto/x509" "errors" "math/big" @@ -164,12 +165,30 @@ func TestUseToken(t *testing.T) { } } +// wrappedProvisioner implements raProvisioner and attProvisioner. +type wrappedProvisioner struct { + provisioner.Interface + raInfo *provisioner.RAInfo +} + +func (p *wrappedProvisioner) RAInfo() *provisioner.RAInfo { + return p.raInfo +} + func TestDB_StoreCertificateChain(t *testing.T) { p := &provisioner.JWK{ ID: "some-id", Name: "admin", Type: "JWK", } + rap := &wrappedProvisioner{ + Interface: p, + raInfo: &provisioner.RAInfo{ + ProvisionerID: "ra-id", + ProvisionerType: "JWK", + ProvisionerName: "ra", + }, + } chain := []*x509.Certificate{ {Raw: []byte("the certificate"), SerialNumber: big.NewInt(1234)}, } @@ -201,6 +220,21 @@ func TestDB_StoreCertificateChain(t *testing.T) { return nil }, }, true}, args{p, chain}, false}, + {"ok ra provisioner", fields{&MockNoSQLDB{ + MUpdate: func(tx *database.Tx) error { + if len(tx.Operations) != 2 { + t.Fatal("unexpected number of operations") + } + assert.Equals(t, []byte("x509_certs"), tx.Operations[0].Bucket) + assert.Equals(t, []byte("1234"), tx.Operations[0].Key) + assert.Equals(t, []byte("the certificate"), tx.Operations[0].Value) + assert.Equals(t, []byte("x509_certs_data"), tx.Operations[1].Bucket) + assert.Equals(t, []byte("1234"), tx.Operations[1].Key) + assert.Equals(t, []byte(`{"provisioner":{"id":"some-id","name":"admin","type":"JWK"},"ra":{"provisionerId":"ra-id","provisionerType":"JWK","provisionerName":"ra"}}`), tx.Operations[1].Value) + assert.Equals(t, `{"provisioner":{"id":"some-id","name":"admin","type":"JWK"},"ra":{"provisionerId":"ra-id","provisionerType":"JWK","provisionerName":"ra"}}`, string(tx.Operations[1].Value)) + return nil + }, + }, true}, args{rap, chain}, false}, {"ok no provisioner", fields{&MockNoSQLDB{ MUpdate: func(tx *database.Tx) error { if len(tx.Operations) != 2 { @@ -293,3 +327,111 @@ func TestDB_GetCertificateData(t *testing.T) { }) } } + +func TestDB_StoreRenewedCertificate(t *testing.T) { + oldCert := &x509.Certificate{SerialNumber: big.NewInt(1)} + chain := []*x509.Certificate{ + &x509.Certificate{SerialNumber: big.NewInt(2), Raw: []byte("raw")}, + &x509.Certificate{SerialNumber: big.NewInt(0)}, + } + + testErr := errors.New("test error") + certsData := []byte(`{"provisioner":{"id":"p","name":"name","type":"JWK"},"ra":{"provisionerId":"rap","provisionerType":"JWK","provisionerName":"rapname"}}`) + matchOperation := func(op *database.TxEntry, bucket, key, value []byte) bool { + return bytes.Equal(op.Bucket, bucket) && bytes.Equal(op.Key, key) && bytes.Equal(op.Value, value) + } + + type fields struct { + DB nosql.DB + isUp bool + } + type args struct { + oldCert *x509.Certificate + chain []*x509.Certificate + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + {"ok", fields{&MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + if bytes.Equal(bucket, certsDataTable) && bytes.Equal(key, []byte("1")) { + return certsData, nil + } + t.Error("ok failed: unexpected get") + return nil, testErr + }, + MUpdate: func(tx *database.Tx) error { + if len(tx.Operations) != 2 { + t.Error("ok failed: unexpected number of operations") + return testErr + } + op0, op1 := tx.Operations[0], tx.Operations[1] + if !matchOperation(op0, certsTable, []byte("2"), []byte("raw")) { + t.Errorf("ok failed: unexpected entry 0, %s[%s]=%s", op0.Bucket, op0.Key, op0.Value) + return testErr + } + if !matchOperation(op1, certsDataTable, []byte("2"), certsData) { + t.Errorf("ok failed: unexpected entry 1, %s[%s]=%s", op1.Bucket, op1.Key, op1.Value) + return testErr + } + return nil + }, + }, true}, args{oldCert, chain}, false}, + {"ok no data", fields{&MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + return nil, database.ErrNotFound + }, + MUpdate: func(tx *database.Tx) error { + if len(tx.Operations) != 1 { + t.Error("ok failed: unexpected number of operations") + return testErr + } + op0 := tx.Operations[0] + if !matchOperation(op0, certsTable, []byte("2"), []byte("raw")) { + t.Errorf("ok failed: unexpected entry 0, %s[%s]=%s", op0.Bucket, op0.Key, op0.Value) + return testErr + } + return nil + }, + }, true}, args{oldCert, chain}, false}, + {"ok fail marshal", fields{&MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + return []byte(`{"bad":"json"`), nil + }, + MUpdate: func(tx *database.Tx) error { + if len(tx.Operations) != 1 { + t.Error("ok failed: unexpected number of operations") + return testErr + } + op0 := tx.Operations[0] + if !matchOperation(op0, certsTable, []byte("2"), []byte("raw")) { + t.Errorf("ok failed: unexpected entry 0, %s[%s]=%s", op0.Bucket, op0.Key, op0.Value) + return testErr + } + return nil + }, + }, true}, args{oldCert, chain}, false}, + {"fail", fields{&MockNoSQLDB{ + MGet: func(bucket, key []byte) ([]byte, error) { + return certsData, nil + }, + MUpdate: func(tx *database.Tx) error { + return testErr + }, + }, true}, args{oldCert, chain}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &DB{ + DB: tt.fields.DB, + isUp: tt.fields.isUp, + } + if err := db.StoreRenewedCertificate(tt.args.oldCert, tt.args.chain...); (err != nil) != tt.wantErr { + t.Errorf("DB.StoreRenewedCertificate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/db/simple.go b/db/simple.go index a7e38de9..6321e86f 100644 --- a/db/simple.go +++ b/db/simple.go @@ -41,6 +41,21 @@ func (s *SimpleDB) Revoke(rci *RevokedCertificateInfo) error { return ErrNotImplemented } +// GetRevokedCertificates returns a "NotImplemented" error. +func (s *SimpleDB) GetRevokedCertificates() (*[]RevokedCertificateInfo, error) { + return nil, ErrNotImplemented +} + +// GetCRL returns a "NotImplemented" error. +func (s *SimpleDB) GetCRL() (*CertificateRevocationListInfo, error) { + return nil, ErrNotImplemented +} + +// StoreCRL returns a "NotImplemented" error. +func (s *SimpleDB) StoreCRL(crlInfo *CertificateRevocationListInfo) error { + return ErrNotImplemented +} + // RevokeSSH returns a "NotImplemented" error. func (s *SimpleDB) RevokeSSH(rci *RevokedCertificateInfo) error { return ErrNotImplemented diff --git a/docker/Dockerfile.step-ca b/docker/Dockerfile.step-ca index 46677a91..ed6b5f56 100644 --- a/docker/Dockerfile.step-ca +++ b/docker/Dockerfile.step-ca @@ -4,6 +4,7 @@ WORKDIR /src COPY . . RUN apk add --no-cache curl git make +RUN make V=1 download RUN make V=1 bin/step-ca bin/step-awskms-init bin/step-cloudkms-init diff --git a/docker/Dockerfile.step-ca.hsm b/docker/Dockerfile.step-ca.hsm index ac59c909..8f413cd7 100644 --- a/docker/Dockerfile.step-ca.hsm +++ b/docker/Dockerfile.step-ca.hsm @@ -5,6 +5,7 @@ COPY . . RUN apk add --no-cache curl git make RUN apk add --no-cache gcc musl-dev pkgconf pcsc-lite-dev +RUN make V=1 download RUN make V=1 GOFLAGS="" build diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 67c5673d..0465f0b7 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -126,17 +126,20 @@ is `json`. * `db`: data persistence layer. See [database documentation](./database.md) for more info. - - - type: `badger`, `bbolt`, `mysql`, etc. - - - dataSource: `string` that can be interpreted differently depending on the - type of the database. Usually a path to where the data is stored. See - the [database configuration docs](./database.md#configuration) for more info. - - - database: name of the database. Used for backends that may have - multiple databases. e.g. MySQL - - - valueDir: directory to store the value log in (Badger specific). + * `type`: `badger`, `bbolt`, `mysql`, etc. + * `dataSource`: string that can be interpreted differently depending on the + type of the database. Usually a path to where the data is stored. See the + [database configuration docs](./database.md#configuration) for more info. + * `database`: name of the database. Used for backends that may have multiple + databases. e.g. MySQL + * `valueDir`: directory to store the value log in (Badger specific). + +* `crl`: Certificate Revocation List settings: + * `enable`: enables CRL generation (`true` to generate, `false` to disable) + * `generateOnRevoke`: a revoke will generate a new CRL if the crl is enabled. + * `cacheDuration`: the duration until next update of the CRL, defaults to 24h. + * `renewPeriod`: the time between CRL regeneration. If not set ~2/3 of the + cacheDuration will be used. * `tls`: settings for negotiating communication with the CA; includes acceptable ciphersuites, min/max TLS version, etc. diff --git a/docs/README.md b/docs/README.md index 5ef683ca..95e4700b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,7 +28,7 @@ Index of Documentation and Tutorials for using and deploying the `step certifica * Check out our [Blog](https://smallstep.com/blog/). We post quality educational content as well as periodic updates on new releases. * **API**: Guides to using the API via the `step` CLI. - * [Revoking Certificates](https://smallstep.com/docs/step-ca/certificate-authority-server-production#x509-certificate-revocation) + * [Revoking Certificates](https://smallstep.com/docs/step-ca/revocation) * [Persistence Layer](https://smallstep.com/docs/step-ca/configuration#databases): description and guide to using `step certificates`' persistence layer for storing certificate management metadata. * **Tutorials**: Guides for deploying and getting started with `step` in various environments. diff --git a/errs/error.go b/errs/error.go index c42e342d..ba066925 100644 --- a/errs/error.go +++ b/errs/error.go @@ -92,7 +92,8 @@ func Wrap(status int, e error, m string, args ...interface{}) error { return nil } _, opts := splitOptionArgs(args) - if err, ok := e.(*Error); ok { + var err *Error + if errors.As(e, &err) { err.Err = errors.Wrap(err.Err, m) e = err } else { @@ -108,7 +109,8 @@ func Wrapf(status int, e error, format string, args ...interface{}) error { return nil } as, opts := splitOptionArgs(args) - if err, ok := e.(*Error); ok { + var err *Error + if errors.As(e, &err) { err.Err = errors.Wrapf(err.Err, format, args...) e = err } else { @@ -141,8 +143,9 @@ func (e *Error) UnmarshalJSON(data []byte) error { // Format implements the fmt.Formatter interface. func (e *Error) Format(f fmt.State, c rune) { - if err, ok := e.Err.(fmt.Formatter); ok { - err.Format(f, c) + var fe fmt.Formatter + if errors.As(e.Err, &fe) { + fe.Format(f, c) return } fmt.Fprint(f, e.Err.Error()) @@ -246,11 +249,13 @@ func New(status int, format string, args ...interface{}) error { // NewError creates a new http error with the given error and message. func NewError(status int, err error, format string, args ...interface{}) error { - if _, ok := err.(*Error); ok { + var e *Error + if errors.As(err, &e) { return err } msg := fmt.Sprintf(format, args...) - if _, ok := err.(log.StackTracedError); !ok { + var ste log.StackTracedError + if !errors.As(err, &ste) { err = errors.Wrap(err, msg) } return &Error{ @@ -263,20 +268,13 @@ func NewError(status int, err error, format string, args ...interface{}) error { // NewErr returns a new Error. If the given error implements the StatusCoder // interface we will ignore the given status. func NewErr(status int, err error, opts ...Option) error { - var ( - e *Error - ok bool - ) - if e, ok = err.(*Error); !ok { - if sc, ok := err.(render.StatusCodedError); ok { - e = &Error{Status: sc.StatusCode(), Err: err} + var e *Error + if !errors.As(err, &e) { + var ste render.StatusCodedError + if errors.As(err, &ste) { + e = &Error{Status: ste.StatusCode(), Err: err} } else { - cause := errors.Cause(err) - if sc, ok := cause.(render.StatusCodedError); ok { - e = &Error{Status: sc.StatusCode(), Err: err} - } else { - e = &Error{Status: status, Err: err} - } + e = &Error{Status: status, Err: err} } } for _, o := range opts { @@ -299,7 +297,8 @@ func Errorf(code int, format string, args ...interface{}) error { // ApplyOptions applies the given options to the error if is the type *Error. // TODO(mariano): try to get rid of this. func ApplyOptions(err error, opts ...interface{}) error { - if e, ok := err.(*Error); ok { + var e *Error + if errors.As(err, &e) { _, o := splitOptionArgs(opts) for _, fn := range o { fn(e) diff --git a/errs/errors_test.go b/errs/errors_test.go index a2accebb..7b83c8d9 100644 --- a/errs/errors_test.go +++ b/errs/errors_test.go @@ -57,6 +57,7 @@ func TestError_UnmarshalJSON(t *testing.T) { if err := e.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { t.Errorf("Error.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } + //nolint:govet // best option if !reflect.DeepEqual(tt.expected, e) { t.Errorf("Error.UnmarshalJSON() wants = %+v, got %+v", tt.expected, e) } diff --git a/go.mod b/go.mod index 7a27a806..a971cc62 100644 --- a/go.mod +++ b/go.mod @@ -3,64 +3,67 @@ module github.com/smallstep/certificates go 1.18 require ( - cloud.google.com/go v0.100.2 - cloud.google.com/go/security v1.3.0 - github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.27 // indirect + cloud.google.com/go v0.105.0 // indirect + cloud.google.com/go/longrunning v0.3.0 + cloud.google.com/go/security v1.10.0 + github.com/Azure/azure-sdk-for-go v67.0.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.28 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Masterminds/sprig/v3 v3.2.2 github.com/ThalesIgnite/crypto11 v1.2.5 // indirect - github.com/aws/aws-sdk-go v1.44.37 // indirect - github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd // indirect + github.com/aws/aws-sdk-go v1.44.127 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/fatih/color v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.4.0 github.com/go-chi/chi v4.1.2+incompatible + github.com/go-kit/kit v0.10.0 // indirect github.com/go-piv/piv-go v1.10.0 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/golang/mock v1.6.0 - github.com/google/go-cmp v0.5.8 + github.com/google/go-cmp v0.5.9 + github.com/google/go-tpm v0.3.3 github.com/google/uuid v1.3.0 - github.com/googleapis/gax-go/v2 v2.4.0 - github.com/hashicorp/vault/api v1.3.1 - github.com/hashicorp/vault/api/auth/approle v0.1.1 - github.com/hashicorp/vault/api/auth/kubernetes v0.1.0 + github.com/googleapis/gax-go/v2 v2.6.0 + github.com/hashicorp/vault/api v1.8.2 + github.com/hashicorp/vault/api/auth/approle v0.3.0 + github.com/hashicorp/vault/api/auth/kubernetes v0.3.0 github.com/jhump/protoreflect v1.9.0 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.13 // indirect github.com/micromdm/scep/v2 v2.1.0 - github.com/newrelic/go-agent/v3 v3.18.0 + github.com/newrelic/go-agent/v3 v3.20.0 github.com/pkg/errors v0.9.1 - github.com/rs/xid v1.2.1 - github.com/sirupsen/logrus v1.8.1 - github.com/slackhq/nebula v1.5.2 + github.com/rs/xid v1.4.0 + github.com/ryboe/q v1.0.17 + github.com/sirupsen/logrus v1.9.0 + github.com/slackhq/nebula v1.6.1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 - github.com/smallstep/nosql v0.4.0 - github.com/stretchr/testify v1.7.1 - github.com/urfave/cli v1.22.4 + github.com/smallstep/nosql v0.5.0 + github.com/stretchr/testify v1.8.1 + github.com/urfave/cli v1.22.10 go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 - go.step.sm/cli-utils v0.7.4 - go.step.sm/crypto v0.19.0 - go.step.sm/linkedca v0.19.0-rc.1 - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 - golang.org/x/net v0.0.0-20220607020251-c690dde0001d + go.step.sm/cli-utils v0.7.5 + go.step.sm/crypto v0.23.0 + go.step.sm/linkedca v0.19.0-rc.4 + golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b + golang.org/x/net v0.0.0-20221014081412-f15817d10f9b + golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect - google.golang.org/api v0.84.0 - google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad - google.golang.org/grpc v1.47.0 - google.golang.org/protobuf v1.28.0 + google.golang.org/api v0.102.0 + google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect + google.golang.org/grpc v1.50.1 + google.golang.org/protobuf v1.28.1 gopkg.in/square/go-jose.v2 v2.6.0 ) require ( - github.com/google/go-tpm v0.3.3 - github.com/ryboe/q v1.0.17 -) - -require ( - cloud.google.com/go/compute v1.6.1 // indirect - cloud.google.com/go/iam v0.1.0 // indirect - cloud.google.com/go/kms v1.4.0 // indirect - filippo.io/edwards25519 v1.0.0-rc.1 // indirect + cloud.google.com/go/compute v1.12.1 // indirect + cloud.google.com/go/compute/metadata v0.2.1 // indirect + cloud.google.com/go/iam v0.6.0 // indirect + cloud.google.com/go/kms v1.5.0 // indirect + filippo.io/edwards25519 v1.0.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect @@ -75,6 +78,7 @@ require ( github.com/armon/go-radix v1.0.0 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -83,45 +87,43 @@ require ( github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/fatih/color v1.7.0 // indirect - github.com/go-kit/kit v0.9.0 // indirect - github.com/go-logfmt/logfmt v0.5.0 // indirect - github.com/go-sql-driver/mysql v1.5.0 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect + github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v0.16.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-retryablehttp v0.6.6 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 // indirect - github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-version v1.2.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/vault/sdk v0.3.0 // indirect + github.com/hashicorp/vault/sdk v0.6.0 // indirect github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.10.1 // indirect + github.com/jackc/pgconn v1.13.0 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.2.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.1 // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.9.0 // indirect - github.com/jackc/pgx/v4 v4.14.0 // indirect + github.com/jackc/pgtype v1.12.0 // indirect + github.com/jackc/pgx/v4 v4.17.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/klauspost/compress v1.12.3 // indirect + github.com/klauspost/compress v1.15.11 // indirect github.com/kr/text v0.2.0 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect @@ -129,7 +131,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect - github.com/mitchellh/mapstructure v1.4.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect @@ -145,17 +147,18 @@ require ( go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect - golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect - golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect + golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect + golang.org/x/text v0.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect - gopkg.in/yaml.v3 v3.0.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) // replace github.com/smallstep/nosql => ../nosql // replace go.step.sm/crypto => ../crypto + // replace go.step.sm/cli-utils => ../cli-utils // replace go.step.sm/linkedca => ../linkedca // use github.com/smallstep/pkcs7 fork with patches applied -replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20211016004704-52592125d6f6 +replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20221024180420-e1aab68dda05 diff --git a/go.sum b/go.sum index 0e4aaee6..9bf298d5 100644 --- a/go.sum +++ b/go.sum @@ -1,75 +1,30 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= -cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v0.1.0 h1:W2vbGCrE3Z7J/x3WXLxxGl9LMSB2uhsAA7Ss/6u/qRY= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/kms v1.4.0 h1:iElbfoE61VeLhnZcGOltqL8HIly8Nhbe5t6JlH9GXjo= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/security v1.3.0 h1:BhCl33x+KQI4qiZnFrfr2gAGhb2aZ0ZvKB3Y4QlEfgo= -cloud.google.com/go/security v1.3.0/go.mod h1:pQsnLAXfMzuWVJdctBs8BV3tGd3Jr0SMYu6KK3QXYAs= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= -filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/iam v0.6.0 h1:nsqQC88kT5Iwlm4MeNGTpfMWddp6NB/UOLFTH6m1QfQ= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/kms v1.5.0 h1:uc58n3b/n/F2yDMJzHMbXORkJSh3fzO4/+jju6eR7Zg= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/security v1.10.0 h1:KSKzzJMyUoMRQzcz7azIgqAUqxo7rmQ5rYvimMhikqg= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= +filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v67.0.0+incompatible h1:SVBwznSETB0Sipd0uyGJr7khLhJOFRUEUb+0JgkCvDo= +github.com/Azure/azure-sdk-for-go v67.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= -github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= -github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= +github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= +github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA= @@ -90,8 +45,8 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -103,32 +58,48 @@ github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmy github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18= github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.44.37 h1:KvDxCX6dfJeEDC77U5GPGSP0ErecmNnhDHFxw+NIvlI= -github.com/aws/aws-sdk-go v1.44.37/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.44.127 h1:IoO2VfuIQg1aMXnl8l6OpNUKT4Qq5CnJMOyIWoTYXj0= +github.com/aws/aws-sdk-go v1.44.127/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= @@ -137,24 +108,23 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWs github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -171,8 +141,8 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd h1:KoJOtZf+6wpQaDTuOWGuo61GxcPBIfhwRxRTaTWGCTc= -github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= @@ -180,23 +150,28 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUn github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= @@ -207,23 +182,24 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-piv/piv-go v1.10.0 h1:P1Y1VjBI5DnXW0+YkKmTuh5opWnMIrKriUaIOblee9Q= github.com/go-piv/piv-go v1.10.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -231,26 +207,23 @@ github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -258,7 +231,6 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -268,9 +240,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -280,17 +252,12 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-tpm v0.1.2-0.20190725015402-ae6dd98980d4/go.mod h1:H9HbmUG2YgV/PHITkO7p6wxEEj/v5nlsVWIwumwH2NI= github.com/google/go-tpm v0.3.0/go.mod h1:iVLWvrPp/bHeEkxTFi9WG6K9w0iy2yIszHwZGHPbzAw= github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo= @@ -298,48 +265,34 @@ github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51B github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0= github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa h1:7MYGT2XEMam7Mtzv1yDUYXANedWvwk3HKkR3MyGowy8= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -354,60 +307,74 @@ github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= +github.com/hashicorp/go-kms-wrapping/entropy/v2 v2.0.0/go.mod h1:xvb32K2keAc+R8DSFG2IwDcydK9DBQE+fGA5fsw6hSk= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= +github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1 h1:78ki3QBevHwYrVxnyVeaEz+7WtifHhauYF23es/0KlI= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.1 h1:nd0HIW15E6FG1MsnArYaHfuw9C2zgzM8LxkG5Ty/788= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.3.0/go.mod h1:EabNQLI0VWbWoGlA+oBLC8PXmR9D60aUVgQGvangFWQ= -github.com/hashicorp/vault/api v1.3.1 h1:pkDkcgTh47PRjY1NEFeofqR4W/HkNUi9qIakESO2aRM= -github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw= -github.com/hashicorp/vault/api/auth/approle v0.1.1 h1:R5yA+xcNvw1ix6bDuWOaLOq2L4L77zDCVsethNw97xQ= -github.com/hashicorp/vault/api/auth/approle v0.1.1/go.mod h1:mHOLgh//xDx4dpqXoq6tS8Ob0FoCFWLU2ibJ26Lfmag= -github.com/hashicorp/vault/api/auth/kubernetes v0.1.0 h1:6BtyahbF4aQp8gg3ww0A/oIoqzbhpNP1spXU3nHE0n0= -github.com/hashicorp/vault/api/auth/kubernetes v0.1.0/go.mod h1:Pdgk78uIs0mgDOLvc3a+h/vYIT9rznw2sz+ucuH9024= -github.com/hashicorp/vault/sdk v0.3.0 h1:kR3dpxNkhh/wr6ycaJYqp6AFT/i2xaftbfnwZduTKEY= -github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= +github.com/hashicorp/vault/api v1.8.2 h1:C7OL9YtOtwQbTKI9ogB0A1wffRbCN+rH/LLCHO3d8HM= +github.com/hashicorp/vault/api v1.8.2/go.mod h1:ML8aYzBIhY5m1MD1B2Q0JV89cC85YVH4t5kBaZiyVaE= +github.com/hashicorp/vault/api/auth/approle v0.3.0 h1:Ib0oCNXsCq/QZhPYtXPzJEbGS5WR/KoZf8c84QoFdkU= +github.com/hashicorp/vault/api/auth/approle v0.3.0/go.mod h1:hm51TbjzUkPO0Y17wkrpwOpvyyMRpXJNueTHiG04t3k= +github.com/hashicorp/vault/api/auth/kubernetes v0.3.0 h1:HkaCmTKzcgLa2tjdiAid1rbmyQNmQGHfnmvIIM2WorY= +github.com/hashicorp/vault/api/auth/kubernetes v0.3.0/go.mod h1:l1B4MGtLc+P37MabBQiIhP3qd9agj0vqhETmaQjjC/Y= +github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs= +github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -418,8 +385,9 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= +github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -435,44 +403,51 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= +github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.9.0 h1:/SH1RxEtltvJgsDqp3TbiTFApD3mey3iygpuEGeuBXk= github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= +github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.14.0 h1:TgdrmgnM7VY72EuSQzBbBd4JA1RLqJolrw9nQVZABVc= github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8= +github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= +github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jhump/protoreflect v1.9.0 h1:npqHz788dryJiR/l6K/RUQAyh2SwV91+d1dnh4RjO9w= github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -491,6 +466,9 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= @@ -501,6 +479,7 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -509,11 +488,13 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/micromdm/scep/v2 v2.1.0 h1:2fS9Rla7qRR266hvUoEauBJ7J6FhgssEiq2OkSKXmaU= github.com/micromdm/scep/v2 v2.1.0/go.mod h1:BkF7TkPPhmgJAMtHfP+sFTKXmgzNJgLQlvvGoOExBcc= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -521,16 +502,20 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -539,59 +524,99 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/newrelic/go-agent/v3 v3.18.0 h1:AOR3hhF2ZVE0yfvNPuOaEhEvNMYyIfEBY8EizQpnt7g= -github.com/newrelic/go-agent/v3 v3.18.0/go.mod h1:BFJOlbZWRlPTXKYIC1TTTtQKTnYntEJaU0VU507hDc0= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/newrelic/go-agent/v3 v3.20.0 h1:AiV7kFr4kUQZcoZt45zW+W2+iZE0TUsk5S8Uhk5jnsM= +github.com/newrelic/go-agent/v3 v3.20.0/go.mod h1:rT6ZUxJc5rQbWLyCtjqQCOcfb01lKRFbc1yMQkcboWM= github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/ryboe/q v1.0.17 h1:Ap34VxlzBbjFHdApe1RzvBwrYmoLa4hC5J7P643ENtU= github.com/ryboe/q v1.0.17/go.mod h1:7wNegax8bjSyGxm9Pnsy6i8z+Uy9X8hkm7pAId9PDdg= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -600,18 +625,21 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/slackhq/nebula v1.5.2 h1:wuIOHsOnrNw3rQx8yPxXiGu8wAtAxxtUI/K8W7Vj7EI= -github.com/slackhq/nebula v1.5.2/go.mod h1:xaCM6wqbFk/NRmmUe1bv88fWBm3a1UioXJVIpR52WlE= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM= +github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI= github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= -github.com/smallstep/nosql v0.4.0 h1:Go3WYwttUuvwqMtFiiU4g7kBIlY+hR0bIZAqVdakQ3M= -github.com/smallstep/nosql v0.4.0/go.mod h1:yKZT5h7cdIVm6wEKM9+jN5dgK80Hljpuy8HNsnI7Gzo= -github.com/smallstep/pkcs7 v0.0.0-20211016004704-52592125d6f6 h1:8Rjy6IZbSM/jcYgBWCoLIGjug7QcoLtF9sUuhDrHD2U= -github.com/smallstep/pkcs7 v0.0.0-20211016004704-52592125d6f6/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +github.com/smallstep/nosql v0.5.0 h1:1BPyHy8bha8qSaxgULGEdqhXpNFXimAfudnauFVqmxw= +github.com/smallstep/nosql v0.5.0/go.mod h1:yKZT5h7cdIVm6wEKM9+jN5dgK80Hljpuy8HNsnI7Gzo= +github.com/smallstep/pkcs7 v0.0.0-20221024180420-e1aab68dda05 h1:nVZXaJTwrUcfPUSZknkOidfITqOXSO0wE8pkOUTOdSM= +github.com/smallstep/pkcs7 v0.0.0-20221024180420-e1aab68dda05/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -620,63 +648,72 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= -github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= +github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.step.sm/cli-utils v0.7.4 h1:oI7PStZqlvjPZ0u2EB4lN7yZ4R3ShTotdGL/L84Oorg= -go.step.sm/cli-utils v0.7.4/go.mod h1:taSsY8haLmXoXM3ZkywIyRmVij/4Aj0fQbNTlJvv71I= +go.step.sm/cli-utils v0.7.5 h1:jyp6X8k8mN1B0uWJydTid0C++8tQhm2kaaAdXKQQzdk= +go.step.sm/cli-utils v0.7.5/go.mod h1:taSsY8haLmXoXM3ZkywIyRmVij/4Aj0fQbNTlJvv71I= go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= -go.step.sm/crypto v0.19.0 h1:WxjUDeTDpuPZ1IR3v6c4jc6WdlQlS5IYYQBhfnG5uW0= -go.step.sm/crypto v0.19.0/go.mod h1:qZ+pNU1nV+THwP7TPTNCRMRr9xrRURhETTAK7U5psfw= -go.step.sm/linkedca v0.19.0-rc.1 h1:8XcQvanelK1g0ijl5/itmmAIsqD2QSMHGqcWzJwwJCU= -go.step.sm/linkedca v0.19.0-rc.1/go.mod h1:G35baT7Qnh6VsRCjzSfi5xsYw0ERrU+I1aIuZswMBeA= +go.step.sm/crypto v0.23.0 h1:pkkAlQxeDs+7qZ0mWSnN25qbtDm/AH6u0hYlwcmRWng= +go.step.sm/crypto v0.23.0/go.mod h1:sK4iH/xyQDbffE1jCgj5hraVrbdKY9CTs0Lnjskxnk4= +go.step.sm/linkedca v0.19.0-rc.4 h1:kaBW+xHkRRgMNDa4gWiIj7gBq5yjbJKGlTWYYo5z2KQ= +go.step.sm/linkedca v0.19.0-rc.4/go.mod h1:b7vWPrHfYLEOTSUZitFEcztVCpTc+ileIN85CwEAluM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -691,11 +728,12 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -705,449 +743,186 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0= +golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= +golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b h1:NXqSWXSRUSCaFuvitrWtU169I3876zRTalMRbfd6LL0= -golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.84.0 h1:NMB9J4cCxs9xEm+1Z9QiO3eFvn7EnQj3Eo3hN6ugVlg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad h1:kqrS+lhvaMHCxul6sKQvKJ8nAAhlVItmZV822hYFH/U= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1156,25 +931,29 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1186,15 +965,12 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/make/docker.mk b/make/docker.mk deleted file mode 100644 index edb82423..00000000 --- a/make/docker.mk +++ /dev/null @@ -1,91 +0,0 @@ -######################################### -# Building Docker Image -# -# This uses a multi-stage build file. The first stage is a builder (that might -# be large in size). After the build has succeeded, the statically linked -# binary is copied to a new image that is optimized for size. -######################################### - -ifeq (, $(shell which docker)) - DOCKER_CLIENT_OS := linux -else - DOCKER_CLIENT_OS := $(strip $(shell docker version -f '{{.Client.Os}}' 2>/dev/null)) -endif - -DOCKER_PLATFORMS = linux/amd64,linux/386,linux/arm,linux/arm64 -DOCKER_IMAGE_NAME = smallstep/step-ca - -docker-prepare: - # Ensure, we can build for ARM architecture -ifeq (linux,$(DOCKER_CLIENT_OS)) - [ -f /proc/sys/fs/binfmt_misc/qemu-arm ] || docker run --rm --privileged linuxkit/binfmt:v0.8-amd64 -endif - - # Register buildx builder - mkdir -p $$HOME/.docker/cli-plugins - - test -f $$HOME/.docker/cli-plugins/docker-buildx || \ - (wget -q -O $$HOME/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.$(DOCKER_CLIENT_OS)-amd64 && \ - chmod +x $$HOME/.docker/cli-plugins/docker-buildx) - - docker buildx create --use --name mybuilder --platform="$(DOCKER_PLATFORMS)" || true - -.PHONY: docker-prepare - -################################################# -# Releasing Docker Images -# -# Using the docker build infrastructure, this section is responsible for -# logging into docker hub. -################################################# - -# Rely on DOCKER_USERNAME and DOCKER_PASSWORD being set inside the CI or -# equivalent environment -docker-login: - $Q docker login -u="$(DOCKER_USERNAME)" -p="$(DOCKER_PASSWORD)" - -.PHONY: docker-login - -################################################# -# Targets for different type of builds -################################################# - -define DOCKER_BUILDX - # $(1) -- Image Tag - # $(2) -- Push (empty is no push | --push will push to dockerhub) - docker buildx build . --progress plain -t $(DOCKER_IMAGE_NAME):$(1) -f docker/Dockerfile.step-ca --platform="$(DOCKER_PLATFORMS)" $(2) - echo -n "$(COSIGN_PWD)" | cosign sign -key /tmp/cosign.key -r $(DOCKER_IMAGE_NAME):$(1) - -endef - -# For non-master builds don't build the docker containers. -docker-branch: - -# For master builds don't build the docker containers. -docker-master: - -# For all builds with a release candidate tag build and push the containers. -docker-release-candidate: docker-prepare docker-login - $(call DOCKER_BUILDX,$(VERSION),--push) - -# For all builds with a release tag build and push the containers. -docker-release: docker-prepare docker-login - $(call DOCKER_BUILDX,latest,--push) - $(call DOCKER_BUILDX,$(VERSION),--push) - -.PHONY: docker-branch docker-master docker-release-candidate docker-release - -# XXX We put the output for the build in 'output' so we don't mess with how we -# do rule overriding from the base Makefile (if you name it 'build' it messes up -# the wildcarding). -DOCKER_OUTPUT=$(OUTPUT_ROOT)docker/ - -DOCKER_MAKE=V=$V GOOS_OVERRIDE='GOOS=linux GOARCH=amd64' PREFIX=$(1) make $(1)bin/$(BINNAME) -DOCKER_BUILD=$Q docker build -t $(DOCKER_IMAGE_NAME):latest -f docker/Dockerfile.step-ca --build-arg BINPATH=$(DOCKER_OUTPUT)bin/$(BINNAME) . - -docker-dev: docker/Dockerfile.step-ca - mkdir -p $(DOCKER_OUTPUT) - $(call DOCKER_MAKE,$(DOCKER_OUTPUT),step-ca) - $(call DOCKER_BUILD) - -.PHONY: docker-dev diff --git a/pki/helm.go b/pki/helm.go index e13bb97c..72d95971 100644 --- a/pki/helm.go +++ b/pki/helm.go @@ -17,6 +17,7 @@ type helmVariables struct { Defaults *linkedca.Defaults Password string EnableSSH bool + EnableAdmin bool TLS authconfig.TLSOptions Provisioners []provisioner.Interface } @@ -34,14 +35,39 @@ func (p *PKI) WriteHelmTemplate(w io.Writer) error { p.Ssh = nil } - // Convert provisioner to ca.json - provisioners := make([]provisioner.Interface, len(p.Authority.Provisioners)) - for i, p := range p.Authority.Provisioners { + // Convert provisioners to ca.json representation + provisioners := []provisioner.Interface{} + for _, p := range p.Authority.Provisioners { pp, err := authority.ProvisionerToCertificates(p) if err != nil { return err } - provisioners[i] = pp + provisioners = append(provisioners, pp) + } + + // Add default ACME provisioner if enabled. Note that this logic is similar + // to what's in p.GenerateConfig(), but that codepath isn't taken when + // writing the Helm template. The default JWK provisioner is added earlier in + // the process and that's part of the provisioners above. + // TODO(hs): consider refactoring the initialization, so that this becomes + // easier to reason about and maintain. + if p.options.enableACME { + provisioners = append(provisioners, &provisioner.ACME{ + Type: "ACME", + Name: "acme", + }) + } + + // Add default SSHPOP provisioner if enabled. Similar to the above, this is + // the same as what happens in p.GenerateConfig(). + if p.options.enableSSH { + provisioners = append(provisioners, &provisioner.SSHPOP{ + Type: "SSHPOP", + Name: "sshpop", + Claims: &provisioner.Claims{ + EnableSSHCA: &p.options.enableSSH, + }, + }) } if err := tmpl.Execute(w, helmVariables{ @@ -49,6 +75,7 @@ func (p *PKI) WriteHelmTemplate(w io.Writer) error { Defaults: &p.Defaults, Password: "", EnableSSH: p.options.enableSSH, + EnableAdmin: p.options.enableAdmin, TLS: authconfig.DefaultTLSOptions, Provisioners: provisioners, }); err != nil { @@ -88,6 +115,7 @@ inject: type: badgerv2 dataSource: /home/step/db authority: + enableAdmin: {{ .EnableAdmin }} provisioners: {{- range .Provisioners }} - {{ . | toJson }} diff --git a/pki/helm_test.go b/pki/helm_test.go new file mode 100644 index 00000000..ea1c4acd --- /dev/null +++ b/pki/helm_test.go @@ -0,0 +1,232 @@ +package pki + +import ( + "bytes" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/json" + "encoding/pem" + "os" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + + "go.step.sm/crypto/jose" + "go.step.sm/linkedca" + + "github.com/smallstep/certificates/cas/apiv1" +) + +func TestPKI_WriteHelmTemplate(t *testing.T) { + var preparePKI = func(t *testing.T, opts ...Option) *PKI { + o := apiv1.Options{ + Type: "softcas", + IsCreator: true, + } + + // Add default WithHelm option + opts = append(opts, WithHelm()) + + // TODO(hs): invoking `New` doesn't perform all operations that are executed + // when `ca init --helm` is executed. Ideally this logic should be handled + // in one place and probably inside of the PKI initialization. For testing + // purposes the missing operations to fill a Helm template fully are faked + // by `setKeyPair`, `setCertificates` and `setSSHSigningKeys` + p, err := New(o, opts...) + assert.NoError(t, err) + + // setKeyPair sets a predefined JWK and a default JWK provisioner. This is one + // of the things performed in the `ca init` code that's not part of `New`, but + // performed after that in p.GenerateKeyPairs`. We're currently using the same + // JWK for every test to keep test variance small: we're not testing JWK generation + // here after all. It's a bit dangerous to redefine the function here, but it's + // the simplest way to make this fully testable without refactoring the init now. + // The password for the predefined encrypted key is \x01\x03\x03\x07. + setKeyPair(t, p) + + // setCertificates sets some static intermediate and root CA certificate bytes. It + // replaces the logic executed in `p.GenerateRootCertificate`, `p.WriteRootCertificate`, + // and `p.GenerateIntermediateCertificate`. + setCertificates(t, p) + + // setSSHSigningKeys sets predefined SSH user and host certificate and key bytes. + // This replaces the logic in `p.GenerateSSHSigningKeys` + setSSHSigningKeys(t, p) + + return p + } + type test struct { + pki *PKI + testFile string + wantErr bool + } + var tests = map[string]func(t *testing.T) test{ + "ok/simple": func(t *testing.T) test { + return test{ + pki: preparePKI(t), + testFile: "testdata/helm/simple.yml", + wantErr: false, + } + }, + "ok/with-provisioner": func(t *testing.T) test { + return test{ + pki: preparePKI(t, WithProvisioner("a-provisioner")), + testFile: "testdata/helm/with-provisioner.yml", + wantErr: false, + } + }, + "ok/with-acme": func(t *testing.T) test { + return test{ + pki: preparePKI(t, WithACME()), + testFile: "testdata/helm/with-acme.yml", + wantErr: false, + } + }, + "ok/with-admin": func(t *testing.T) test { + return test{ + pki: preparePKI(t, WithAdmin()), + testFile: "testdata/helm/with-admin.yml", + wantErr: false, + } + }, + "ok/with-ssh": func(t *testing.T) test { + return test{ + pki: preparePKI(t, WithSSH()), + testFile: "testdata/helm/with-ssh.yml", + wantErr: false, + } + }, + "ok/with-ssh-and-acme": func(t *testing.T) test { + return test{ + pki: preparePKI(t, WithSSH(), WithACME()), + testFile: "testdata/helm/with-ssh-and-acme.yml", + wantErr: false, + } + }, + "fail/authority.ProvisionerToCertificates": func(t *testing.T) test { + pki := preparePKI(t) + pki.Authority.Provisioners = append(pki.Authority.Provisioners, + &linkedca.Provisioner{ + Type: linkedca.Provisioner_JWK, + Name: "Broken JWK", + Details: nil, + }, + ) + return test{ + pki: pki, + wantErr: true, + } + }, + } + for name, run := range tests { + tc := run(t) + t.Run(name, func(t *testing.T) { + + w := &bytes.Buffer{} + if err := tc.pki.WriteHelmTemplate(w); (err != nil) != tc.wantErr { + t.Errorf("PKI.WriteHelmTemplate() error = %v, wantErr %v", err, tc.wantErr) + return + } + + if tc.wantErr { + // don't compare output if an error was expected on output + return + } + + wantBytes, err := os.ReadFile(tc.testFile) + assert.NoError(t, err) + if diff := cmp.Diff(wantBytes, w.Bytes()); diff != "" { + t.Logf("Generated Helm template did not match reference %q\n", tc.testFile) + t.Errorf("Diff follows:\n%s\n", diff) + t.Errorf("Full output:\n%s\n", w.Bytes()) + } + }) + } +} + +// setKeyPair sets a predefined JWK and a default JWK provisioner. +func setKeyPair(t *testing.T, p *PKI) { + t.Helper() + + var err error + + p.ottPublicKey, err = jose.ParseKey([]byte(`{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"}`)) + if err != nil { + t.Fatal(err) + } + + p.ottPrivateKey, err = jose.ParseEncrypted("eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA") + if err != nil { + t.Fatal(err) + } + + var claims *linkedca.Claims + if p.options.enableSSH { + claims = &linkedca.Claims{ + Ssh: &linkedca.SSHClaims{ + Enabled: true, + }, + } + } + + publicKey, err := json.Marshal(p.ottPublicKey) + if err != nil { + t.Fatal(err) + } + encryptedKey, err := p.ottPrivateKey.CompactSerialize() + if err != nil { + t.Fatal(err) + } + p.Authority.Provisioners = append(p.Authority.Provisioners, &linkedca.Provisioner{ + Type: linkedca.Provisioner_JWK, + Name: p.options.provisioner, + Claims: claims, + Details: &linkedca.ProvisionerDetails{ + Data: &linkedca.ProvisionerDetails_JWK{ + JWK: &linkedca.JWKProvisioner{ + PublicKey: publicKey, + EncryptedPrivateKey: []byte(encryptedKey), + }, + }, + }, + }) +} + +// setCertificates sets some static, gibberish intermediate and root CA certificate and key bytes. +func setCertificates(t *testing.T, p *PKI) { + raw := []byte("these are just some fake root CA cert bytes") + p.Files[p.Root[0]] = encodeCertificate(&x509.Certificate{Raw: raw}) + p.Files[p.RootKey[0]] = pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: []byte("these are just some fake root CA key bytes"), + }) + p.Files[p.Intermediate] = encodeCertificate(&x509.Certificate{Raw: []byte("these are just some fake intermediate CA cert bytes")}) + p.Files[p.IntermediateKey] = pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: []byte("these are just some fake intermediate CA key bytes"), + }) + sum := sha256.Sum256(raw) + p.Defaults.Fingerprint = strings.ToLower(hex.EncodeToString(sum[:])) +} + +// setSSHSigningKeys sets some static, gibberish ssh user and host CA certificate and key bytes. +func setSSHSigningKeys(t *testing.T, p *PKI) { + + if !p.options.enableSSH { + return + } + + p.Files[p.Ssh.HostKey] = pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: []byte("fake ssh host key bytes"), + }) + p.Files[p.Ssh.HostPublicKey] = []byte("ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ0IdS5sZm6KITBMZLEJD6b5ROVraYHcAOr3feFel8r1Wp4DRPR1oU0W00J/zjNBRBbANlJoYN4x/8WNNVZ49Ms=") + p.Files[p.Ssh.UserKey] = pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: []byte("fake ssh user key bytes"), + }) + p.Files[p.Ssh.UserPublicKey] = []byte("ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEWA1qUxaGwVNErsvEOGe2d6TvLMF+aiVpuOiIEvpMJ3JeJmecLQctjWqeIbpSvy6/gRa7c82Ge5rLlapYmOChs=") +} diff --git a/pki/pki.go b/pki/pki.go index 4f3b2127..d6c15c9e 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -176,6 +176,7 @@ func GetProvisionerKey(caURL, rootFile, kid string) (string, error) { type options struct { provisioner string + superAdminSubject string pkiOnly bool enableACME bool enableSSH bool @@ -220,6 +221,15 @@ func WithProvisioner(s string) Option { } } +// WithSuperAdminSubject defines the subject of the first +// super admin for use with the Admin API. The admin will belong +// to the first JWK provisioner. +func WithSuperAdminSubject(s string) Option { + return func(p *PKI) { + p.options.superAdminSubject = s + } +} + // WithPKIOnly will only generate the PKI without the step-ca config files. func WithPKIOnly() Option { return func(p *PKI) { @@ -307,6 +317,9 @@ type PKI struct { // New creates a new PKI configuration. func New(o apiv1.Options, opts ...Option) (*PKI, error) { + // TODO(hs): invoking `New` with a context active will use values from + // that CA context while generating the context. Thay may or may not + // be fully expected and/or what we want. Check that. currentCtx := step.Contexts().GetCurrent() caService, err := cas.New(context.Background(), o) if err != nil { @@ -389,7 +402,7 @@ func New(o apiv1.Options, opts ...Option) (*PKI, error) { if port == "443" || p.options.isHelm { p.Defaults.CaUrl = fmt.Sprintf("https://%s", p.Defaults.CaUrl) } else { - p.Defaults.CaUrl = fmt.Sprintf("https://%s:%s", p.Defaults.CaUrl, port) + p.Defaults.CaUrl = fmt.Sprintf("https://%s", net.JoinHostPort(p.Defaults.CaUrl, port)) } } @@ -645,7 +658,7 @@ func (p *PKI) GetCertificateAuthority() error { // SSH user certificates and a private key used for signing host certificates. func (p *PKI) GenerateSSHSigningKeys(password []byte) error { // Enable SSH - p.options.enableSSH = true + p.options.enableSSH = true // TODO(hs): change this function to not mutate configuration state // Create SSH key used to sign host certificates. Using // kmsapi.UnspecifiedSignAlgorithm will default to the default algorithm. @@ -883,6 +896,11 @@ func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) { // // Note that we might want to be able to define the database as a // flag in `step ca init` so we can write to the proper place. + // + // TODO(hs): the logic for creating the provisioners and the super admin + // is similar to what's done when automatically migrating the provisioners. + // This is related to the existing comment above. Refactor this to exist in + // a single place and ensure it happens only once. _db, err := db.New(cfg.DB) if err != nil { return nil, err @@ -906,9 +924,13 @@ func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) { } } // Add the first provisioner as an admin. + superAdminSubject := "step" + if p.options.superAdminSubject != "" { + superAdminSubject = p.options.superAdminSubject + } if err := adminDB.CreateAdmin(context.Background(), &linkedca.Admin{ AuthorityId: admin.DefaultAuthorityID, - Subject: "step", + Subject: superAdminSubject, Type: linkedca.Admin_SUPER_ADMIN, ProvisionerId: adminID, }); err != nil { @@ -991,6 +1013,18 @@ func (p *PKI) Save(opt ...ConfigOption) error { ui.PrintSelected("Default profile configuration", p.profileDefaults) } ui.PrintSelected("Certificate Authority configuration", p.config) + if cfg.AuthorityConfig.EnableAdmin && p.options.deploymentType != LinkedDeployment { + // TODO(hs): we may want to get this information from the DB, because that's + // where the admin and provisioner are stored in this case. Requires some + // refactoring. + superAdminSubject := "step" + if p.options.superAdminSubject != "" { + superAdminSubject = p.options.superAdminSubject + } + ui.PrintSelected("Admin provisioner", fmt.Sprintf("%s (JWK)", p.options.provisioner)) + ui.PrintSelected("Super admin subject", superAdminSubject) + } + if p.options.deploymentType != LinkedDeployment { ui.Println() if p.casOptions.Is(apiv1.SoftCAS) { diff --git a/pki/testdata/helm/simple.yml b/pki/testdata/helm/simple.yml new file mode 100644 index 00000000..8a7e369f --- /dev/null +++ b/pki/testdata/helm/simple.yml @@ -0,0 +1,81 @@ +# Helm template +inject: + enabled: true + # Config contains the configuration files ca.json and defaults.json + config: + files: + ca.json: + root: /home/step/certs/root_ca.crt + federateRoots: [] + crt: /home/step/certs/intermediate_ca.crt + key: /home/step/secrets/intermediate_ca_key + address: 127.0.0.1:9000 + dnsNames: + - 127.0.0.1 + logger: + format: json + db: + type: badgerv2 + dataSource: /home/step/db + authority: + enableAdmin: false + provisioners: + - {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","options":{"x509":{},"ssh":{}}} + tls: + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + minVersion: 1.2 + maxVersion: 1.3 + renegotiation: false + + defaults.json: + ca-url: https://127.0.0.1 + ca-config: /home/step/config/ca.json + fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3 + root: /home/step/certs/root_ca.crt + + # Certificates contains the root and intermediate certificate and + # optionally the SSH host and user public keys + certificates: + # intermediate_ca contains the text of the intermediate CA Certificate + intermediate_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5 + dGVz + -----END CERTIFICATE----- + + + # root_ca contains the text of the root CA Certificate + root_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw== + -----END CERTIFICATE----- + + + # Secrets contains the root and intermediate keys and optionally the SSH + # private keys + secrets: + # ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key + # This value must be base64 encoded. + ca_password: + provisioner_password: + + x509: + # intermediate_ca_key contains the contents of your encrypted intermediate CA key + intermediate_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0 + ZXM= + -----END EC PRIVATE KEY----- + + + # root_ca_key contains the contents of your encrypted root CA key + # Note that this value can be omitted without impacting the functionality of step-certificates + # If supplied, this should be encrypted using a unique password that is not used for encrypting + # the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key. + root_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz + -----END EC PRIVATE KEY----- + diff --git a/pki/testdata/helm/with-acme.yml b/pki/testdata/helm/with-acme.yml new file mode 100644 index 00000000..488bc32f --- /dev/null +++ b/pki/testdata/helm/with-acme.yml @@ -0,0 +1,82 @@ +# Helm template +inject: + enabled: true + # Config contains the configuration files ca.json and defaults.json + config: + files: + ca.json: + root: /home/step/certs/root_ca.crt + federateRoots: [] + crt: /home/step/certs/intermediate_ca.crt + key: /home/step/secrets/intermediate_ca_key + address: 127.0.0.1:9000 + dnsNames: + - 127.0.0.1 + logger: + format: json + db: + type: badgerv2 + dataSource: /home/step/db + authority: + enableAdmin: false + provisioners: + - {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","options":{"x509":{},"ssh":{}}} + - {"type":"ACME","name":"acme"} + tls: + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + minVersion: 1.2 + maxVersion: 1.3 + renegotiation: false + + defaults.json: + ca-url: https://127.0.0.1 + ca-config: /home/step/config/ca.json + fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3 + root: /home/step/certs/root_ca.crt + + # Certificates contains the root and intermediate certificate and + # optionally the SSH host and user public keys + certificates: + # intermediate_ca contains the text of the intermediate CA Certificate + intermediate_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5 + dGVz + -----END CERTIFICATE----- + + + # root_ca contains the text of the root CA Certificate + root_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw== + -----END CERTIFICATE----- + + + # Secrets contains the root and intermediate keys and optionally the SSH + # private keys + secrets: + # ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key + # This value must be base64 encoded. + ca_password: + provisioner_password: + + x509: + # intermediate_ca_key contains the contents of your encrypted intermediate CA key + intermediate_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0 + ZXM= + -----END EC PRIVATE KEY----- + + + # root_ca_key contains the contents of your encrypted root CA key + # Note that this value can be omitted without impacting the functionality of step-certificates + # If supplied, this should be encrypted using a unique password that is not used for encrypting + # the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key. + root_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz + -----END EC PRIVATE KEY----- + diff --git a/pki/testdata/helm/with-admin.yml b/pki/testdata/helm/with-admin.yml new file mode 100644 index 00000000..790fbdd4 --- /dev/null +++ b/pki/testdata/helm/with-admin.yml @@ -0,0 +1,81 @@ +# Helm template +inject: + enabled: true + # Config contains the configuration files ca.json and defaults.json + config: + files: + ca.json: + root: /home/step/certs/root_ca.crt + federateRoots: [] + crt: /home/step/certs/intermediate_ca.crt + key: /home/step/secrets/intermediate_ca_key + address: 127.0.0.1:9000 + dnsNames: + - 127.0.0.1 + logger: + format: json + db: + type: badgerv2 + dataSource: /home/step/db + authority: + enableAdmin: true + provisioners: + - {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","options":{"x509":{},"ssh":{}}} + tls: + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + minVersion: 1.2 + maxVersion: 1.3 + renegotiation: false + + defaults.json: + ca-url: https://127.0.0.1 + ca-config: /home/step/config/ca.json + fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3 + root: /home/step/certs/root_ca.crt + + # Certificates contains the root and intermediate certificate and + # optionally the SSH host and user public keys + certificates: + # intermediate_ca contains the text of the intermediate CA Certificate + intermediate_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5 + dGVz + -----END CERTIFICATE----- + + + # root_ca contains the text of the root CA Certificate + root_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw== + -----END CERTIFICATE----- + + + # Secrets contains the root and intermediate keys and optionally the SSH + # private keys + secrets: + # ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key + # This value must be base64 encoded. + ca_password: + provisioner_password: + + x509: + # intermediate_ca_key contains the contents of your encrypted intermediate CA key + intermediate_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0 + ZXM= + -----END EC PRIVATE KEY----- + + + # root_ca_key contains the contents of your encrypted root CA key + # Note that this value can be omitted without impacting the functionality of step-certificates + # If supplied, this should be encrypted using a unique password that is not used for encrypting + # the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key. + root_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz + -----END EC PRIVATE KEY----- + diff --git a/pki/testdata/helm/with-provisioner.yml b/pki/testdata/helm/with-provisioner.yml new file mode 100644 index 00000000..de17ef0a --- /dev/null +++ b/pki/testdata/helm/with-provisioner.yml @@ -0,0 +1,81 @@ +# Helm template +inject: + enabled: true + # Config contains the configuration files ca.json and defaults.json + config: + files: + ca.json: + root: /home/step/certs/root_ca.crt + federateRoots: [] + crt: /home/step/certs/intermediate_ca.crt + key: /home/step/secrets/intermediate_ca_key + address: 127.0.0.1:9000 + dnsNames: + - 127.0.0.1 + logger: + format: json + db: + type: badgerv2 + dataSource: /home/step/db + authority: + enableAdmin: false + provisioners: + - {"type":"JWK","name":"a-provisioner","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","options":{"x509":{},"ssh":{}}} + tls: + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + minVersion: 1.2 + maxVersion: 1.3 + renegotiation: false + + defaults.json: + ca-url: https://127.0.0.1 + ca-config: /home/step/config/ca.json + fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3 + root: /home/step/certs/root_ca.crt + + # Certificates contains the root and intermediate certificate and + # optionally the SSH host and user public keys + certificates: + # intermediate_ca contains the text of the intermediate CA Certificate + intermediate_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5 + dGVz + -----END CERTIFICATE----- + + + # root_ca contains the text of the root CA Certificate + root_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw== + -----END CERTIFICATE----- + + + # Secrets contains the root and intermediate keys and optionally the SSH + # private keys + secrets: + # ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key + # This value must be base64 encoded. + ca_password: + provisioner_password: + + x509: + # intermediate_ca_key contains the contents of your encrypted intermediate CA key + intermediate_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0 + ZXM= + -----END EC PRIVATE KEY----- + + + # root_ca_key contains the contents of your encrypted root CA key + # Note that this value can be omitted without impacting the functionality of step-certificates + # If supplied, this should be encrypted using a unique password that is not used for encrypting + # the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key. + root_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz + -----END EC PRIVATE KEY----- + diff --git a/pki/testdata/helm/with-ssh-and-acme.yml b/pki/testdata/helm/with-ssh-and-acme.yml new file mode 100644 index 00000000..639aca6a --- /dev/null +++ b/pki/testdata/helm/with-ssh-and-acme.yml @@ -0,0 +1,105 @@ +# Helm template +inject: + enabled: true + # Config contains the configuration files ca.json and defaults.json + config: + files: + ca.json: + root: /home/step/certs/root_ca.crt + federateRoots: [] + crt: /home/step/certs/intermediate_ca.crt + key: /home/step/secrets/intermediate_ca_key + ssh: + hostKey: /home/step/secrets/ssh_host_ca_key + userKey: /home/step/secrets/ssh_user_ca_key + address: 127.0.0.1:9000 + dnsNames: + - 127.0.0.1 + logger: + format: json + db: + type: badgerv2 + dataSource: /home/step/db + authority: + enableAdmin: false + provisioners: + - {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","claims":{"enableSSHCA":true,"disableRenewal":false,"allowRenewalAfterExpiry":false},"options":{"x509":{},"ssh":{}}} + - {"type":"ACME","name":"acme"} + - {"type":"SSHPOP","name":"sshpop","claims":{"enableSSHCA":true}} + tls: + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + minVersion: 1.2 + maxVersion: 1.3 + renegotiation: false + + defaults.json: + ca-url: https://127.0.0.1 + ca-config: /home/step/config/ca.json + fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3 + root: /home/step/certs/root_ca.crt + + # Certificates contains the root and intermediate certificate and + # optionally the SSH host and user public keys + certificates: + # intermediate_ca contains the text of the intermediate CA Certificate + intermediate_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5 + dGVz + -----END CERTIFICATE----- + + + # root_ca contains the text of the root CA Certificate + root_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw== + -----END CERTIFICATE----- + + # ssh_host_ca contains the text of the public ssh key for the SSH root CA + ssh_host_ca: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ0IdS5sZm6KITBMZLEJD6b5ROVraYHcAOr3feFel8r1Wp4DRPR1oU0W00J/zjNBRBbANlJoYN4x/8WNNVZ49Ms= + + # ssh_user_ca contains the text of the public ssh key for the SSH root CA + ssh_user_ca: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEWA1qUxaGwVNErsvEOGe2d6TvLMF+aiVpuOiIEvpMJ3JeJmecLQctjWqeIbpSvy6/gRa7c82Ge5rLlapYmOChs= + + # Secrets contains the root and intermediate keys and optionally the SSH + # private keys + secrets: + # ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key + # This value must be base64 encoded. + ca_password: + provisioner_password: + + x509: + # intermediate_ca_key contains the contents of your encrypted intermediate CA key + intermediate_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0 + ZXM= + -----END EC PRIVATE KEY----- + + + # root_ca_key contains the contents of your encrypted root CA key + # Note that this value can be omitted without impacting the functionality of step-certificates + # If supplied, this should be encrypted using a unique password that is not used for encrypting + # the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key. + root_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz + -----END EC PRIVATE KEY----- + + ssh: + # ssh_host_ca_key contains the contents of your encrypted SSH Host CA key + host_ca_key: | + -----BEGIN EC PRIVATE KEY----- + ZmFrZSBzc2ggaG9zdCBrZXkgYnl0ZXM= + -----END EC PRIVATE KEY----- + + + # ssh_user_ca_key contains the contents of your encrypted SSH User CA key + user_ca_key: | + -----BEGIN EC PRIVATE KEY----- + ZmFrZSBzc2ggdXNlciBrZXkgYnl0ZXM= + -----END EC PRIVATE KEY----- + diff --git a/pki/testdata/helm/with-ssh.yml b/pki/testdata/helm/with-ssh.yml new file mode 100644 index 00000000..2e4845f0 --- /dev/null +++ b/pki/testdata/helm/with-ssh.yml @@ -0,0 +1,104 @@ +# Helm template +inject: + enabled: true + # Config contains the configuration files ca.json and defaults.json + config: + files: + ca.json: + root: /home/step/certs/root_ca.crt + federateRoots: [] + crt: /home/step/certs/intermediate_ca.crt + key: /home/step/secrets/intermediate_ca_key + ssh: + hostKey: /home/step/secrets/ssh_host_ca_key + userKey: /home/step/secrets/ssh_user_ca_key + address: 127.0.0.1:9000 + dnsNames: + - 127.0.0.1 + logger: + format: json + db: + type: badgerv2 + dataSource: /home/step/db + authority: + enableAdmin: false + provisioners: + - {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","claims":{"enableSSHCA":true,"disableRenewal":false,"allowRenewalAfterExpiry":false},"options":{"x509":{},"ssh":{}}} + - {"type":"SSHPOP","name":"sshpop","claims":{"enableSSHCA":true}} + tls: + cipherSuites: + - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + minVersion: 1.2 + maxVersion: 1.3 + renegotiation: false + + defaults.json: + ca-url: https://127.0.0.1 + ca-config: /home/step/config/ca.json + fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3 + root: /home/step/certs/root_ca.crt + + # Certificates contains the root and intermediate certificate and + # optionally the SSH host and user public keys + certificates: + # intermediate_ca contains the text of the intermediate CA Certificate + intermediate_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5 + dGVz + -----END CERTIFICATE----- + + + # root_ca contains the text of the root CA Certificate + root_ca: | + -----BEGIN CERTIFICATE----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw== + -----END CERTIFICATE----- + + # ssh_host_ca contains the text of the public ssh key for the SSH root CA + ssh_host_ca: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ0IdS5sZm6KITBMZLEJD6b5ROVraYHcAOr3feFel8r1Wp4DRPR1oU0W00J/zjNBRBbANlJoYN4x/8WNNVZ49Ms= + + # ssh_user_ca contains the text of the public ssh key for the SSH root CA + ssh_user_ca: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEWA1qUxaGwVNErsvEOGe2d6TvLMF+aiVpuOiIEvpMJ3JeJmecLQctjWqeIbpSvy6/gRa7c82Ge5rLlapYmOChs= + + # Secrets contains the root and intermediate keys and optionally the SSH + # private keys + secrets: + # ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key + # This value must be base64 encoded. + ca_password: + provisioner_password: + + x509: + # intermediate_ca_key contains the contents of your encrypted intermediate CA key + intermediate_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0 + ZXM= + -----END EC PRIVATE KEY----- + + + # root_ca_key contains the contents of your encrypted root CA key + # Note that this value can be omitted without impacting the functionality of step-certificates + # If supplied, this should be encrypted using a unique password that is not used for encrypting + # the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key. + root_ca_key: | + -----BEGIN EC PRIVATE KEY----- + dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz + -----END EC PRIVATE KEY----- + + ssh: + # ssh_host_ca_key contains the contents of your encrypted SSH Host CA key + host_ca_key: | + -----BEGIN EC PRIVATE KEY----- + ZmFrZSBzc2ggaG9zdCBrZXkgYnl0ZXM= + -----END EC PRIVATE KEY----- + + + # ssh_user_ca_key contains the contents of your encrypted SSH User CA key + user_ca_key: | + -----BEGIN EC PRIVATE KEY----- + ZmFrZSBzc2ggdXNlciBrZXkgYnl0ZXM= + -----END EC PRIVATE KEY----- + diff --git a/policy/engine.go b/policy/engine.go index d1fb4928..c02fd7a9 100755 --- a/policy/engine.go +++ b/policy/engine.go @@ -4,11 +4,13 @@ import ( "crypto/x509" "fmt" "net" + "net/http" "net/url" + "go.step.sm/crypto/x509util" "golang.org/x/crypto/ssh" - "go.step.sm/crypto/x509util" + "github.com/smallstep/certificates/errs" ) type NamePolicyReason int @@ -62,6 +64,22 @@ func (e *NamePolicyError) Error() string { } } +// As implements the As(any) bool interface and allows to use "errors.As()" to +// convert a NotAllowed NamePolicyError to an errs.Error. +func (e *NamePolicyError) As(v any) bool { + if e.Reason == NotAllowed { + if err, ok := v.(**errs.Error); ok { + *err = &errs.Error{ + Status: http.StatusForbidden, + Msg: fmt.Sprintf("The request was forbidden by the certificate authority: %s", e.Error()), + Err: e, + } + return true + } + } + return false +} + func (e *NamePolicyError) Detail() string { return e.detail } @@ -73,7 +91,6 @@ func (e *NamePolicyError) Detail() string { // TODO(hs): implement matching URI schemes, paths, etc; not just the domain part of URI domains type NamePolicyEngine struct { - // verifySubjectCommonName is set when Subject Common Name must be verified verifySubjectCommonName bool // allowLiteralWildcardNames allows literal wildcard DNS domains @@ -107,7 +124,6 @@ type NamePolicyEngine struct { // NewNamePolicyEngine creates a new NamePolicyEngine with NamePolicyOptions func New(opts ...NamePolicyOption) (*NamePolicyEngine, error) { - e := &NamePolicyEngine{} for _, option := range opts { if err := option(e); err != nil { @@ -153,7 +169,6 @@ func New(opts ...NamePolicyOption) (*NamePolicyEngine, error) { // duplicate values removed. It retains the order of elements // in the source slice. func removeDuplicates(items []string) (ret []string) { - // no need to remove dupes; return original if len(items) <= 1 { return items @@ -179,7 +194,6 @@ func removeDuplicates(items []string) (ret []string) { // the source slice. An IPNet is considered duplicate if its CIDR // notation exists multiple times in the slice. func removeDuplicateIPNets(items []*net.IPNet) (ret []*net.IPNet) { - // no need to remove dupes; return original if len(items) <= 1 { return items diff --git a/policy/options_117_test.go b/policy/options_117_test.go deleted file mode 100644 index 916eefe2..00000000 --- a/policy/options_117_test.go +++ /dev/null @@ -1,125 +0,0 @@ -//go:build !go1.18 -// +build !go1.18 - -package policy - -import "testing" - -func Test_normalizeAndValidateURIDomainConstraint(t *testing.T) { - tests := []struct { - name string - constraint string - want string - wantErr bool - }{ - { - name: "fail/empty-constraint", - constraint: "", - want: "", - wantErr: true, - }, - { - name: "fail/scheme-https", - constraint: `https://*.local`, - want: "", - wantErr: true, - }, - { - name: "fail/too-many-asterisks", - constraint: "**.local", - want: "", - wantErr: true, - }, - { - name: "fail/empty-label", - constraint: "..local", - want: "", - wantErr: true, - }, - { - name: "fail/empty-reverse", - constraint: ".", - want: "", - wantErr: true, - }, - { - name: "fail/no-asterisk", - constraint: ".example.com", - want: "", - wantErr: true, - }, - { - name: "fail/domain-with-port", - constraint: "host.local:8443", - want: "", - wantErr: true, - }, - { - name: "fail/ipv4", - constraint: "127.0.0.1", - want: "", - wantErr: true, - }, - { - name: "fail/ipv6-brackets", - constraint: "[::1]", - want: "", - wantErr: true, - }, - { - name: "fail/ipv6-no-brackets", - constraint: "::1", - want: "", - wantErr: true, - }, - { - name: "fail/ipv6-no-brackets", - constraint: "[::1", - want: "", - wantErr: true, - }, - { - name: "fail/idna-internationalized-domain-name-lookup", - constraint: `\00local`, - want: "", - wantErr: true, - }, - { - name: "ok/wildcard", - constraint: "*.local", - want: ".local", - wantErr: false, - }, - { - name: "ok/specific-domain", - constraint: "example.local", - want: "example.local", - wantErr: false, - }, - { - name: "ok/idna-internationalized-domain-name-lookup", - constraint: `*.bücher.example.com`, - want: ".xn--bcher-kva.example.com", - wantErr: false, - }, - { - // IDNA2003 vs. 2008 deviation: https://unicode.org/reports/tr46/#Deviations results - // in a difference between Go 1.18 and lower versions. Go 1.18 expects ".xn--fa-hia.de"; not .fass.de. - name: "ok/idna-internationalized-domain-name-lookup-deviation", - constraint: `*.faß.de`, - want: ".fass.de", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := normalizeAndValidateURIDomainConstraint(tt.constraint) - if (err != nil) != tt.wantErr { - t.Errorf("normalizeAndValidateURIDomainConstraint() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("normalizeAndValidateURIDomainConstraint() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/policy/options_118_test.go b/policy/options_118_test.go deleted file mode 100644 index 6fa2ded4..00000000 --- a/policy/options_118_test.go +++ /dev/null @@ -1,125 +0,0 @@ -//go:build go1.18 -// +build go1.18 - -package policy - -import "testing" - -func Test_normalizeAndValidateURIDomainConstraint(t *testing.T) { - tests := []struct { - name string - constraint string - want string - wantErr bool - }{ - { - name: "fail/empty-constraint", - constraint: "", - want: "", - wantErr: true, - }, - { - name: "fail/scheme-https", - constraint: `https://*.local`, - want: "", - wantErr: true, - }, - { - name: "fail/too-many-asterisks", - constraint: "**.local", - want: "", - wantErr: true, - }, - { - name: "fail/empty-label", - constraint: "..local", - want: "", - wantErr: true, - }, - { - name: "fail/empty-reverse", - constraint: ".", - want: "", - wantErr: true, - }, - { - name: "fail/domain-with-port", - constraint: "host.local:8443", - want: "", - wantErr: true, - }, - { - name: "fail/no-asterisk", - constraint: ".example.com", - want: "", - wantErr: true, - }, - { - name: "fail/ipv4", - constraint: "127.0.0.1", - want: "", - wantErr: true, - }, - { - name: "fail/ipv6-brackets", - constraint: "[::1]", - want: "", - wantErr: true, - }, - { - name: "fail/ipv6-no-brackets", - constraint: "::1", - want: "", - wantErr: true, - }, - { - name: "fail/ipv6-no-brackets", - constraint: "[::1", - want: "", - wantErr: true, - }, - { - name: "fail/idna-internationalized-domain-name-lookup", - constraint: `\00local`, - want: "", - wantErr: true, - }, - { - name: "ok/wildcard", - constraint: "*.local", - want: ".local", - wantErr: false, - }, - { - name: "ok/specific-domain", - constraint: "example.local", - want: "example.local", - wantErr: false, - }, - { - name: "ok/idna-internationalized-domain-name-lookup", - constraint: `*.bücher.example.com`, - want: ".xn--bcher-kva.example.com", - wantErr: false, - }, - { - // IDNA2003 vs. 2008 deviation: https://unicode.org/reports/tr46/#Deviations results - // in a difference between Go 1.18 and lower versions. Go 1.18 expects ".xn--fa-hia.de"; not .fass.de. - name: "ok/idna-internationalized-domain-name-lookup-deviation", - constraint: `*.faß.de`, - want: ".xn--fa-hia.de", - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := normalizeAndValidateURIDomainConstraint(tt.constraint) - if (err != nil) != tt.wantErr { - t.Errorf("normalizeAndValidateURIDomainConstraint() error = %v, wantErr %v", err, tt.wantErr) - } - if got != tt.want { - t.Errorf("normalizeAndValidateURIDomainConstraint() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/policy/options_test.go b/policy/options_test.go index 697afecf..c8b05aa6 100644 --- a/policy/options_test.go +++ b/policy/options_test.go @@ -658,3 +658,122 @@ func TestNew(t *testing.T) { }) } } + +func Test_normalizeAndValidateURIDomainConstraint(t *testing.T) { + tests := []struct { + name string + constraint string + want string + wantErr bool + }{ + { + name: "fail/empty-constraint", + constraint: "", + want: "", + wantErr: true, + }, + { + name: "fail/scheme-https", + constraint: `https://*.local`, + want: "", + wantErr: true, + }, + { + name: "fail/too-many-asterisks", + constraint: "**.local", + want: "", + wantErr: true, + }, + { + name: "fail/empty-label", + constraint: "..local", + want: "", + wantErr: true, + }, + { + name: "fail/empty-reverse", + constraint: ".", + want: "", + wantErr: true, + }, + { + name: "fail/domain-with-port", + constraint: "host.local:8443", + want: "", + wantErr: true, + }, + { + name: "fail/no-asterisk", + constraint: ".example.com", + want: "", + wantErr: true, + }, + { + name: "fail/ipv4", + constraint: "127.0.0.1", + want: "", + wantErr: true, + }, + { + name: "fail/ipv6-brackets", + constraint: "[::1]", + want: "", + wantErr: true, + }, + { + name: "fail/ipv6-no-brackets", + constraint: "::1", + want: "", + wantErr: true, + }, + { + name: "fail/ipv6-no-brackets", + constraint: "[::1", + want: "", + wantErr: true, + }, + { + name: "fail/idna-internationalized-domain-name-lookup", + constraint: `\00local`, + want: "", + wantErr: true, + }, + { + name: "ok/wildcard", + constraint: "*.local", + want: ".local", + wantErr: false, + }, + { + name: "ok/specific-domain", + constraint: "example.local", + want: "example.local", + wantErr: false, + }, + { + name: "ok/idna-internationalized-domain-name-lookup", + constraint: `*.bücher.example.com`, + want: ".xn--bcher-kva.example.com", + wantErr: false, + }, + { + // IDNA2003 vs. 2008 deviation: https://unicode.org/reports/tr46/#Deviations results + // in a difference between Go 1.18 and lower versions. Go 1.18 expects ".xn--fa-hia.de"; not .fass.de. + name: "ok/idna-internationalized-domain-name-lookup-deviation", + constraint: `*.faß.de`, + want: ".xn--fa-hia.de", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := normalizeAndValidateURIDomainConstraint(tt.constraint) + if (err != nil) != tt.wantErr { + t.Errorf("normalizeAndValidateURIDomainConstraint() error = %v, wantErr %v", err, tt.wantErr) + } + if got != tt.want { + t.Errorf("normalizeAndValidateURIDomainConstraint() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/policy/validate.go b/policy/validate.go index ee6f7e9c..f7cf6e70 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -8,6 +8,7 @@ package policy import ( "bytes" + "errors" "fmt" "net" "net/url" @@ -21,7 +22,6 @@ import ( // validateNames verifies that all names are allowed. func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailAddresses []string, uris []*url.URL, principals []string) error { - // nothing to compare against; return early if e.totalNumberOfConstraints == 0 { return nil @@ -182,7 +182,6 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA // validateCommonName verifies that the Subject Common Name is allowed func (e *NamePolicyEngine) validateCommonName(commonName string) error { - // nothing to compare against; return early if e.totalNumberOfConstraints == 0 { return nil @@ -212,7 +211,8 @@ func (e *NamePolicyEngine) validateCommonName(commonName string) error { err := e.validateNames(dnsNames, ips, emails, uris, []string{}) - if pe, ok := err.(*NamePolicyError); ok { + var pe *NamePolicyError + if errors.As(err, &pe) { // override the name type with CN pe.NameType = CNNameType } @@ -229,7 +229,6 @@ func checkNameConstraints( parsedName interface{}, match func(parsedName, constraint interface{}) (match bool, err error), permitted, excluded interface{}) error { - excludedValue := reflect.ValueOf(excluded) for i := 0; i < excludedValue.Len(); i++ { @@ -552,7 +551,6 @@ func (e *NamePolicyEngine) matchDomainConstraint(domain, constraint string) (boo // SOURCE: https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/crypto/x509/verify.go func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) { - // TODO(hs): this is code from Go library, but I got some unexpected result: // with permitted net 127.0.0.0/24, 127.0.0.1 is NOT allowed. When parsing 127.0.0.1 as net.IP // which is in the IPAddresses slice, the underlying length is 16. The contraint.IP has a length diff --git a/scep/api/api.go b/scep/api/api.go index b738a933..346b9c75 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -350,7 +350,6 @@ func formatCapabilities(caps []string) []byte { // writeResponse writes a SCEP response back to the SCEP client. func writeResponse(w http.ResponseWriter, res Response) { - if res.Error != nil { log.Error(w, res.Error) } diff --git a/scep/authority.go b/scep/authority.go index 7dbbb8c5..585b937e 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -71,7 +71,6 @@ type SignAuthority interface { // New returns a new Authority that implements the SCEP interface. func New(signAuth SignAuthority, ops AuthorityOptions) (*Authority, error) { - authority := &Authority{ prefix: ops.Prefix, dns: ops.DNS, @@ -145,7 +144,6 @@ func (a *Authority) getLinkExplicit(provisionerName string, abs bool, baseURL *u // GetCACertificates returns the certificate (chain) for the CA func (a *Authority) GetCACertificates(ctx context.Context) ([]*x509.Certificate, error) { - // TODO: this should return: the "SCEP Server (RA)" certificate, the issuing CA up to and excl. the root // Some clients do need the root certificate however; also see: https://github.com/openxpki/openxpki/issues/73 // @@ -283,6 +281,14 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m if err != nil { return nil, fmt.Errorf("error retrieving authorization options from SCEP provisioner: %w", err) } + // Unlike most of the provisioners, scep's AuthorizeSign method doesn't + // define the templates, and the template data used in WebHooks is not + // available. + for _, signOp := range signOps { + if wc, ok := signOp.(*provisioner.WebhookController); ok { + wc.TemplateData = data + } + } opts := provisioner.SignOptions{} templateOptions, err := provisioner.TemplateOptions(p.GetOptions(), data) @@ -385,7 +391,6 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m // CreateFailureResponse creates an appropriately signed reply for PKI operations func (a *Authority) CreateFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *PKIMessage, info FailInfoName, infoText string) (*PKIMessage, error) { - config := pkcs7.SignerInfoConfig{ ExtraSignedAttributes: []pkcs7.Attribute{ { @@ -471,7 +476,6 @@ func (a *Authority) MatchChallengePassword(ctx context.Context, password string) // GetCACaps returns the CA capabilities func (a *Authority) GetCACaps(ctx context.Context) []string { - p, err := provisionerFromContext(ctx) if err != nil { return defaultCapabilities diff --git a/scep/options.go b/scep/options.go index 752b309a..201f1beb 100644 --- a/scep/options.go +++ b/scep/options.go @@ -20,7 +20,6 @@ type Options struct { // Validate checks the fields in Options. func (o *Options) Validate() error { - if o.CertificateChain == nil { return errors.New("certificate chain not configured correctly") } diff --git a/scep/service.go b/scep/service.go index 508bcf77..a4efe27e 100644 --- a/scep/service.go +++ b/scep/service.go @@ -14,7 +14,6 @@ type Service struct { } func NewService(ctx context.Context, opts Options) (*Service, error) { - if err := opts.Validate(); err != nil { return nil, err } diff --git a/webhook/options.go b/webhook/options.go new file mode 100644 index 00000000..88c44986 --- /dev/null +++ b/webhook/options.go @@ -0,0 +1,97 @@ +package webhook + +import ( + "crypto/x509" + + "go.step.sm/crypto/sshutil" + "go.step.sm/crypto/x509util" + "golang.org/x/crypto/ssh" +) + +type RequestBodyOption func(*RequestBody) error + +func NewRequestBody(options ...RequestBodyOption) (*RequestBody, error) { + rb := &RequestBody{} + + for _, fn := range options { + if err := fn(rb); err != nil { + return nil, err + } + } + + return rb, nil +} + +func WithX509CertificateRequest(cr *x509.CertificateRequest) RequestBodyOption { + return func(rb *RequestBody) error { + rb.X509CertificateRequest = &X509CertificateRequest{ + CertificateRequest: x509util.NewCertificateRequestFromX509(cr), + PublicKeyAlgorithm: cr.PublicKeyAlgorithm.String(), + Raw: cr.Raw, + } + if cr.PublicKey != nil { + key, err := x509.MarshalPKIXPublicKey(cr.PublicKey) + if err != nil { + return err + } + rb.X509CertificateRequest.PublicKey = key + } + + return nil + } +} + +func WithX509Certificate(cert *x509util.Certificate, leaf *x509.Certificate) RequestBodyOption { + return func(rb *RequestBody) error { + rb.X509Certificate = &X509Certificate{ + Certificate: cert, + PublicKeyAlgorithm: leaf.PublicKeyAlgorithm.String(), + NotBefore: leaf.NotBefore, + NotAfter: leaf.NotAfter, + } + if leaf.PublicKey != nil { + key, err := x509.MarshalPKIXPublicKey(leaf.PublicKey) + if err != nil { + return err + } + rb.X509Certificate.PublicKey = key + } + + return nil + } +} + +func WithAttestationData(data *AttestationData) RequestBodyOption { + return func(rb *RequestBody) error { + rb.AttestationData = data + return nil + } +} + +func WithSSHCertificateRequest(cr sshutil.CertificateRequest) RequestBodyOption { + return func(rb *RequestBody) error { + rb.SSHCertificateRequest = &SSHCertificateRequest{ + Type: cr.Type, + KeyID: cr.KeyID, + Principals: cr.Principals, + } + if cr.Key != nil { + rb.SSHCertificateRequest.PublicKey = cr.Key.Marshal() + } + return nil + } +} + +func WithSSHCertificate(cert *sshutil.Certificate, certTpl *ssh.Certificate) RequestBodyOption { + return func(rb *RequestBody) error { + rb.SSHCertificate = &SSHCertificate{ + Certificate: cert, + ValidBefore: certTpl.ValidBefore, + ValidAfter: certTpl.ValidAfter, + } + if certTpl.Key != nil { + rb.SSHCertificate.PublicKey = certTpl.Key.Marshal() + } + return nil + } +} diff --git a/webhook/options_test.go b/webhook/options_test.go new file mode 100644 index 00000000..e813bb44 --- /dev/null +++ b/webhook/options_test.go @@ -0,0 +1,116 @@ +package webhook + +import ( + "crypto/x509" + "crypto/x509/pkix" + "testing" + "time" + + "github.com/smallstep/assert" + "go.step.sm/crypto/sshutil" + "go.step.sm/crypto/x509util" + "golang.org/x/crypto/ssh" +) + +func TestNewRequestBody(t *testing.T) { + t1 := time.Now() + t2 := t1.Add(time.Hour) + + type test struct { + options []RequestBodyOption + want *RequestBody + wantErr bool + } + tests := map[string]test{ + "Permanent Identifier": { + options: []RequestBodyOption{WithAttestationData(&AttestationData{PermanentIdentifier: "mydevice123"})}, + want: &RequestBody{ + AttestationData: &AttestationData{ + PermanentIdentifier: "mydevice123", + }, + }, + wantErr: false, + }, + "X509 Certificate Request": { + options: []RequestBodyOption{ + WithX509CertificateRequest(&x509.CertificateRequest{ + PublicKeyAlgorithm: x509.ECDSA, + Subject: pkix.Name{CommonName: "foo"}, + Raw: []byte("csr der"), + }), + }, + want: &RequestBody{ + X509CertificateRequest: &X509CertificateRequest{ + CertificateRequest: &x509util.CertificateRequest{ + PublicKeyAlgorithm: x509.ECDSA, + Subject: x509util.Subject{CommonName: "foo"}, + }, + PublicKeyAlgorithm: "ECDSA", + Raw: []byte("csr der"), + }, + }, + wantErr: false, + }, + "X509 Certificate": { + options: []RequestBodyOption{ + WithX509Certificate(&x509util.Certificate{}, &x509.Certificate{ + NotBefore: t1, + NotAfter: t2, + PublicKeyAlgorithm: x509.ECDSA, + }), + }, + want: &RequestBody{ + X509Certificate: &X509Certificate{ + Certificate: &x509util.Certificate{}, + PublicKeyAlgorithm: "ECDSA", + NotBefore: t1, + NotAfter: t2, + }, + }, + }, + "SSH Certificate Request": { + options: []RequestBodyOption{ + WithSSHCertificateRequest(sshutil.CertificateRequest{ + Type: "User", + KeyID: "key1", + Principals: []string{"areed", "other"}, + })}, + want: &RequestBody{ + SSHCertificateRequest: &SSHCertificateRequest{ + Type: "User", + KeyID: "key1", + Principals: []string{"areed", "other"}, + }, + }, + wantErr: false, + }, + "SSH Certificate": { + options: []RequestBodyOption{ + WithSSHCertificate( + &sshutil.Certificate{}, + &ssh.Certificate{ + ValidAfter: uint64(t1.Unix()), + ValidBefore: uint64(t2.Unix()), + }, + ), + }, + want: &RequestBody{ + SSHCertificate: &SSHCertificate{ + Certificate: &sshutil.Certificate{}, + ValidAfter: uint64(t1.Unix()), + ValidBefore: uint64(t2.Unix()), + }, + }, + wantErr: false, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + got, err := NewRequestBody(test.options...) + if (err != nil) != test.wantErr { + t.Fatalf("Got err %v, wanted %t", err, test.wantErr) + } + assert.Equals(t, test.want, got) + }) + } +} diff --git a/webhook/types.go b/webhook/types.go new file mode 100644 index 00000000..19624f5c --- /dev/null +++ b/webhook/types.go @@ -0,0 +1,71 @@ +package webhook + +import ( + "time" + + "go.step.sm/crypto/sshutil" + "go.step.sm/crypto/x509util" +) + +// ResponseBody is the body returned by webhook servers. +type ResponseBody struct { + Data any `json:"data"` + Allow bool `json:"allow"` +} + +// X509CertificateRequest is the certificate request sent to webhook servers for +// enriching webhooks when signing x509 certificates +type X509CertificateRequest struct { + *x509util.CertificateRequest + PublicKey []byte `json:"publicKey"` + PublicKeyAlgorithm string `json:"publicKeyAlgorithm"` + Raw []byte `json:"raw"` +} + +// X509Certificate is the certificate sent to webhook servers for authorizing +// webhooks when signing x509 certificates +type X509Certificate struct { + *x509util.Certificate + PublicKey []byte `json:"publicKey"` + PublicKeyAlgorithm string `json:"publicKeyAlgorithm"` + NotBefore time.Time `json:"notBefore"` + NotAfter time.Time `json:"notAfter"` +} + +// SSHCertificateRequest is the certificate request sent to webhook servers for +// enriching webhooks when signing SSH certificates +type SSHCertificateRequest struct { + PublicKey []byte `json:"publicKey"` + Type string `json:"type"` + KeyID string `json:"keyID"` + Principals []string `json:"principals"` +} + +// SSHCertificate is the certificate sent to webhook servers for authorizing +// webhooks when signing SSH certificates +type SSHCertificate struct { + *sshutil.Certificate + PublicKey []byte `json:"publicKey"` + SignatureKey []byte `json:"signatureKey"` + ValidBefore uint64 `json:"validBefore"` + ValidAfter uint64 `json:"validAfter"` +} + +// AttestationData is data validated by acme device-attest-01 challenge +type AttestationData struct { + PermanentIdentifier string `json:"permanentIdentifier"` +} + +// RequestBody is the body sent to webhook servers. +type RequestBody struct { + Timestamp time.Time `json:"timestamp"` + // Only set after successfully completing acme device-attest-01 challenge + AttestationData *AttestationData `json:"attestationData,omitempty"` + // Set for most provisioners, but not acme or scep + // Token any `json:"token,omitempty"` + // Exactly one of the remaining fields should be set + X509CertificateRequest *X509CertificateRequest `json:"x509CertificateRequest,omitempty"` + X509Certificate *X509Certificate `json:"x509Certificate,omitempty"` + SSHCertificateRequest *SSHCertificateRequest `json:"sshCertificateRequest,omitempty"` + SSHCertificate *SSHCertificate `json:"sshCertificate,omitempty"` +}