Merge branch 'master' into crl-support
This commit is contained in:
commit
59775fff0c
83 changed files with 4505 additions and 604 deletions
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
|
@ -1,4 +0,0 @@
|
|||
needs triage:
|
||||
- '**' # index.php | src/main.php
|
||||
- '.*' # .gitignore
|
||||
- '.*/**' # .github/workflows/label.yml
|
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
@ -9,7 +9,7 @@ on:
|
|||
pull_request:
|
||||
workflow_call:
|
||||
secrets:
|
||||
GITLEAKS_LICENSE_KEY:
|
||||
CODECOV_TOKEN:
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
|
@ -23,5 +23,4 @@ jobs:
|
|||
os-dependencies: "libpcsclite-dev"
|
||||
run-gitleaks: true
|
||||
run-codeql: true
|
||||
secrets:
|
||||
GITLEAKS_LICENSE_KEY: ${{ secrets.GITLEAKS_LICENSE_KEY }}
|
||||
secrets: inherit
|
||||
|
|
86
.github/workflows/release.yml
vendored
86
.github/workflows/release.yml
vendored
|
@ -8,25 +8,17 @@ on:
|
|||
|
||||
jobs:
|
||||
ci:
|
||||
uses: smallstep/certificates/.github/workflows/ci.yml@main
|
||||
uses: smallstep/certificates/.github/workflows/ci.yml@master
|
||||
secrets: inherit
|
||||
|
||||
create_release:
|
||||
name: Create Release
|
||||
needs: ci
|
||||
#needs: ci
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
debversion: ${{ steps.extract-tag.outputs.DEB_VERSION }}
|
||||
is_prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }}
|
||||
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
|
||||
|
@ -34,8 +26,7 @@ jobs:
|
|||
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
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
|
@ -51,54 +42,33 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
needs: create_release
|
||||
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.7.0
|
||||
with:
|
||||
cosign-release: 'v1.1.0'
|
||||
-
|
||||
name: Write cosign key to disk
|
||||
cosign-release: 'v1.12.1'
|
||||
- name: Write cosign key to disk
|
||||
id: write_key
|
||||
run: echo "${{ secrets.COSIGN_KEY }}" > "/tmp/cosign.key"
|
||||
-
|
||||
name: Get Release Date
|
||||
- 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
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
version: 'v1.7.0'
|
||||
version: 'latest'
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT }}
|
||||
GITHUB_TOKEN: ${{ secrets.GORELEASER_PAT }}
|
||||
COSIGN_PWD: ${{ secrets.COSIGN_PWD }}
|
||||
DEB_VERSION: ${{ needs.create_release.outputs.debversion }}
|
||||
RELEASE_DATE: ${{ steps.release_date.outputs.RELEASE_DATE }}
|
||||
|
||||
build_upload_docker:
|
||||
|
@ -106,25 +76,21 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
needs: ci
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.19'
|
||||
-
|
||||
name: Install cosign
|
||||
check-latest: true
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@v1.1.0
|
||||
with:
|
||||
cosign-release: 'v1.1.0'
|
||||
-
|
||||
name: Write cosign key to disk
|
||||
- name: Write cosign key to disk
|
||||
id: write_key
|
||||
run: echo "${{ secrets.COSIGN_KEY }}" > "/tmp/cosign.key"
|
||||
-
|
||||
name: Build
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
PATH=$PATH:/usr/local/go/bin:/home/admin/go/bin
|
||||
|
|
23
.github/workflows/triage.yml
vendored
23
.github/workflows/triage.yml
vendored
|
@ -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
|
||||
|
|
116
.goreleaser.yml
116
.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 <techadmin@smallstep.com>
|
||||
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
|
||||
|
@ -98,7 +88,7 @@ checksum:
|
|||
signs:
|
||||
- cmd: cosign
|
||||
stdin: '{{ .Env.COSIGN_PWD }}'
|
||||
args: ["sign-blob", "-key=/tmp/cosign.key", "-output=${signature}", "${artifact}"]
|
||||
args: ["sign-blob", "-key=/tmp/cosign.key", "-output-signature=${signature}", "${artifact}"]
|
||||
artifacts: all
|
||||
|
||||
snapshot:
|
||||
|
@ -140,7 +130,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
|
||||
|
||||
|
@ -194,39 +184,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/<repo_owner>/<repo_name>/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
# Default for gitlab is "https://gitlab.com/<repo_owner>/<repo_name>/uploads/{{ .ArtifactUploadHash }}/{{ .ArtifactName }}"
|
||||
# Default for gitea is "https://gitea.com/<repo_owner>/<repo_name>/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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -20,6 +20,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- 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.
|
||||
|
||||
### 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
|
||||
|
|
|
@ -2774,3 +2774,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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -17,7 +17,6 @@ const (
|
|||
// Renew uses the information of certificate in the TLS connection to create a
|
||||
// new one.
|
||||
func Renew(w http.ResponseWriter, r *http.Request) {
|
||||
//nolint:contextcheck // the reqest has the context
|
||||
cert, err := getPeerCertificate(r)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
|
|
|
@ -83,7 +83,6 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) {
|
|||
notBefore := time.Unix(int64(oldCert.ValidAfter), 0)
|
||||
notAfter := time.Unix(int64(oldCert.ValidBefore), 0)
|
||||
|
||||
//nolint:contextcheck // the reqest has the context
|
||||
identity, err := renewIdentityCertificate(r, notBefore, notAfter)
|
||||
if err != nil {
|
||||
render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate"))
|
||||
|
|
|
@ -75,7 +75,6 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) {
|
|||
notBefore := time.Unix(int64(oldCert.ValidAfter), 0)
|
||||
notAfter := time.Unix(int64(oldCert.ValidBefore), 0)
|
||||
|
||||
//nolint:contextcheck // the reqest has the context
|
||||
identity, err := renewIdentityCertificate(r, notBefore, notAfter)
|
||||
if err != nil {
|
||||
render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate"))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
var mustAuthority = func(ctx context.Context) adminAuthority {
|
||||
return authority.MustFromContext(ctx)
|
||||
}
|
||||
|
||||
type router struct {
|
||||
acmeResponder ACMEAdminResponder
|
||||
policyResponder PolicyAdminResponder
|
||||
webhookResponder WebhookAdminResponder
|
||||
}
|
||||
|
||||
type RouterOption func(*router)
|
||||
|
||||
func WithACMEResponder(acmeResponder ACMEAdminResponder) RouterOption {
|
||||
return func(r *router) {
|
||||
r.acmeResponder = acmeResponder
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
235
authority/admin/api/webhook.go
Normal file
235
authority/admin/api/webhook.go
Normal file
|
@ -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)
|
||||
}
|
668
authority/admin/api/webhook_test.go
Normal file
668
authority/admin/api/webhook_test.go
Normal file
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -137,6 +137,7 @@ func TestDB_getDBProvisioner(t *testing.T) {
|
|||
}
|
||||
},
|
||||
"fail/deleted": func(t *testing.T) test {
|
||||
|
||||
now := clock.Now()
|
||||
dbp := &dbProvisioner{
|
||||
ID: provID,
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -46,6 +47,7 @@ type Authority struct {
|
|||
adminDB admin.DB
|
||||
templates *templates.Templates
|
||||
linkedCAToken string
|
||||
webhookClient *http.Client
|
||||
|
||||
// X509 CA
|
||||
password []byte
|
||||
|
@ -76,7 +78,7 @@ type Authority struct {
|
|||
crlStopper chan struct{}
|
||||
crlMutex sync.Mutex
|
||||
|
||||
// Do not re-initialize
|
||||
// If true, do not re-initialize
|
||||
initOnce bool
|
||||
startTime time.Time
|
||||
|
||||
|
@ -94,8 +96,11 @@ type Authority struct {
|
|||
|
||||
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.
|
||||
|
@ -603,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 admin
|
||||
// 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 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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,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"
|
||||
|
|
|
@ -491,7 +491,7 @@ func TestAuthority_authorizeSign(t *testing.T) {
|
|||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1034,7 +1034,7 @@ func TestAuthority_authorizeSSHSign(t *testing.T) {
|
|||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -79,6 +79,9 @@ type Config struct {
|
|||
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
|
||||
|
@ -218,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
|
||||
|
@ -257,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 {
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
// ACMEChallenge represents the supported acme challenges.
|
||||
|
@ -252,22 +253,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 {
|
||||
|
|
|
@ -269,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:
|
||||
|
@ -288,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))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -484,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
|
||||
}
|
||||
|
||||
|
@ -765,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
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -363,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
|
||||
}
|
||||
|
||||
|
@ -431,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
|
||||
}
|
||||
|
||||
|
|
|
@ -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},
|
||||
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -272,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
|
||||
}
|
||||
|
||||
|
@ -437,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
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -297,7 +297,7 @@ func TestJWK_AuthorizeSign(t *testing.T) {
|
|||
}
|
||||
} 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))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -242,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
|
||||
}
|
||||
|
||||
|
@ -287,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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +370,7 @@ func TestK8sSA_AuthorizeSSHSign(t *testing.T) {
|
|||
} 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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -323,7 +323,7 @@ func TestOIDC_AuthorizeSign(t *testing.T) {
|
|||
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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -69,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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
14
authority/provisioner/testdata/certs/foo.crt
vendored
Normal file
14
authority/provisioner/testdata/certs/foo.crt
vendored
Normal file
|
@ -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-----
|
5
authority/provisioner/testdata/secrets/foo.key
vendored
Normal file
5
authority/provisioner/testdata/secrets/foo.key
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIJmnxm3N/ahRA2PWeZhRGJUKPU1lI44WcE4P1bynIim6oAoGCCqGSM49
|
||||
AwEHoUQDQgAEG6bXw6JxWnlD4k6+dsJfa93XM4K3qJcA0ZrIMhYJ69RLih+elkXI
|
||||
B9YI3y9KcmxZTtz3YAbpEeLvrL3qt+NzgA==
|
||||
-----END EC PRIVATE KEY-----
|
209
authority/provisioner/webhook.go
Normal file
209
authority/provisioner/webhook.go
Normal file
|
@ -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
|
||||
}
|
473
authority/provisioner/webhook_test.go
Normal file
473
authority/provisioner/webhook_test.go
Normal file
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,44 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
@ -144,6 +182,7 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner.
|
|||
GetIdentityFunc: a.getIdentityFunc,
|
||||
AuthorizeRenewFunc: a.authorizeRenewFunc,
|
||||
AuthorizeSSHRenewFunc: a.authorizeSSHRenewFunc,
|
||||
WebhookClient: a.webhookClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -493,9 +532,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)
|
||||
|
@ -621,12 +714,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 +733,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 +749,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 {
|
||||
|
@ -879,7 +977,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
|
||||
}
|
||||
|
@ -902,9 +1000,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
|
||||
}
|
||||
|
@ -929,9 +1028,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
|
||||
}
|
||||
|
@ -953,9 +1053,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
|
||||
}
|
||||
|
@ -976,9 +1077,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
|
||||
}
|
||||
|
@ -1002,9 +1104,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
|
||||
}
|
||||
|
@ -1025,9 +1128,10 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro
|
|||
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
|
||||
}
|
||||
|
@ -1045,9 +1149,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
|
||||
}
|
||||
|
@ -1065,6 +1170,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{
|
||||
|
@ -1079,7 +1185,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
|
||||
}
|
||||
|
@ -1102,9 +1208,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
|
||||
}
|
||||
|
@ -1122,6 +1229,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())
|
||||
|
|
|
@ -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,82 @@ 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"github.com/smallstep/certificates/webhook"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -161,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
|
||||
|
@ -185,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)
|
||||
}
|
||||
|
@ -198,6 +204,14 @@ 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 {
|
||||
|
@ -262,6 +276,13 @@ func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisi
|
|||
)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -631,3 +652,37 @@ func (a *Authority) getAddUserCommand(principal string) string {
|
|||
}
|
||||
return strings.ReplaceAll(cmd, "<principal>", 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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
@ -851,8 +865,9 @@ func TestAuthority_GetSSHHosts(t *testing.T) {
|
|||
if err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
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.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 {
|
||||
|
@ -1078,8 +1093,9 @@ func TestAuthority_RekeySSH(t *testing.T) {
|
|||
if err != nil {
|
||||
if assert.NotNil(t, tc.err) {
|
||||
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.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 {
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/nosql/database"
|
||||
"github.com/smallstep/certificates/webhook"
|
||||
)
|
||||
|
||||
// GetTLSOptions returns the tls options configured.
|
||||
|
@ -99,7 +100,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
|
||||
|
@ -137,14 +139,25 @@ 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 {
|
||||
var te *x509util.TemplateError
|
||||
|
@ -229,6 +242,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,6 +264,11 @@ 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 !errors.Is(err, db.ErrNotImplemented) {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err,
|
||||
|
@ -300,7 +326,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,
|
||||
|
@ -884,3 +910,51 @@ func templatingError(err error) error {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -18,8 +18,6 @@ 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"
|
||||
|
@ -61,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]
|
||||
}
|
||||
|
@ -134,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{
|
||||
|
@ -549,6 +563,36 @@ ZYtQ9Ot36qc=
|
|||
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)
|
||||
|
@ -636,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{}
|
||||
|
@ -697,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
|
||||
},
|
||||
|
@ -723,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 {
|
||||
|
@ -844,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),
|
||||
|
@ -893,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)
|
||||
|
@ -955,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)
|
||||
|
@ -1331,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{
|
||||
|
@ -1371,15 +1505,15 @@ func TestAuthority_Revoke(t *testing.T) {
|
|||
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{
|
||||
|
@ -1411,15 +1545,15 @@ func TestAuthority_Revoke(t *testing.T) {
|
|||
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{
|
||||
|
@ -1450,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,
|
||||
|
@ -1542,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,
|
||||
|
|
8
authority/webhook.go
Normal file
8
authority/webhook.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package authority
|
||||
|
||||
import "github.com/smallstep/certificates/webhook"
|
||||
|
||||
type webhookController interface {
|
||||
Enrich(*webhook.RequestBody) error
|
||||
Authorize(*webhook.RequestBody) error
|
||||
}
|
27
authority/webhook_test.go
Normal file
27
authority/webhook_test.go
Normal file
|
@ -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
|
||||
}
|
|
@ -1101,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()
|
||||
|
|
70
ca/ca.go
70
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
|
||||
|
||||
return tlsConfig, nil
|
||||
clientTLSConfig.RootCAs = rootCAsPool
|
||||
|
||||
return serverTLSConfig, clientTLSConfig, nil
|
||||
}
|
||||
|
||||
// shouldServeSCEPEndpoints returns if the CA should be
|
||||
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -143,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())
|
||||
|
@ -253,7 +254,8 @@ func NewError(status int, err error, format string, args ...interface{}) error {
|
|||
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{
|
||||
|
@ -268,15 +270,11 @@ func NewError(status int, err error, format string, args ...interface{}) error {
|
|||
func NewErr(status int, err error, opts ...Option) error {
|
||||
var e *Error
|
||||
if !errors.As(err, &e) {
|
||||
if sc, ok := err.(render.StatusCodedError); ok {
|
||||
e = &Error{Status: sc.StatusCode(), Err: err}
|
||||
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 {
|
||||
|
|
88
go.mod
88
go.mod
|
@ -3,16 +3,16 @@ module github.com/smallstep/certificates
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.102.1
|
||||
cloud.google.com/go/security v1.7.0
|
||||
cloud.google.com/go v0.104.0
|
||||
cloud.google.com/go/security v1.8.0
|
||||
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.27 // 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.111 // 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
|
||||
|
@ -20,46 +20,46 @@ require (
|
|||
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/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.1
|
||||
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.19.2
|
||||
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/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.8.0
|
||||
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.2
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
|
||||
go.step.sm/cli-utils v0.7.5
|
||||
go.step.sm/crypto v0.21.0
|
||||
go.step.sm/linkedca v0.19.0-rc.3
|
||||
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.96.0
|
||||
google.golang.org/genproto v0.0.0-20220929141241-1ce7b20da813
|
||||
google.golang.org/grpc v1.49.0
|
||||
google.golang.org/api v0.100.0
|
||||
google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a
|
||||
google.golang.org/grpc v1.50.1
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.7.0 // indirect
|
||||
cloud.google.com/go/compute v1.10.0 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
cloud.google.com/go/kms v1.4.0 // indirect
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 // 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
|
||||
|
@ -74,6 +74,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
|
||||
|
@ -82,12 +83,13 @@ 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/go-logfmt/logfmt v0.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.1.0 // 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
|
||||
|
@ -97,34 +99,34 @@ require (
|
|||
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/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
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
|
||||
|
@ -139,16 +141,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-20220909003341-f21342109be1 // 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.3.8 // indirect
|
||||
google.golang.org/appengine v1.6.7 // 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
|
||||
|
|
237
go.sum
237
go.sum
|
@ -28,9 +28,8 @@ cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Ud
|
|||
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/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
|
||||
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
|
||||
cloud.google.com/go v0.102.1 h1:vpK6iQWv/2uUeFJth4/cBHsQAGjn1iIE6AAlxipRaA0=
|
||||
cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
|
||||
cloud.google.com/go v0.104.0 h1:gSmWO7DY1vOm0MVU6DNXM11BWHHsTUmsC5cv1fuW5X8=
|
||||
cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
|
||||
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=
|
||||
|
@ -39,11 +38,8 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
|
|||
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/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
||||
cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk=
|
||||
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
|
||||
cloud.google.com/go/compute v1.10.0 h1:aoLIYaA1fX3ywihqpBk2APQKOo20nXsp1GEZQbx5Jk4=
|
||||
cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
|
||||
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/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
|
||||
|
@ -55,17 +51,16 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
|
|||
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.7.0 h1:176N+6wf67OA6HgqhmNN/AfmUtwq50na2VKR6/6l34k=
|
||||
cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=
|
||||
cloud.google.com/go/security v1.8.0 h1:linnRc3/gJYDfKbAtNixVQ52+66DpOx5MmCz0NNxal8=
|
||||
cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
|
||||
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=
|
||||
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
|
||||
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=
|
||||
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=
|
||||
|
@ -73,8 +68,8 @@ github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
|
|||
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=
|
||||
|
@ -133,8 +128,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
|
|||
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.37 h1:KvDxCX6dfJeEDC77U5GPGSP0ErecmNnhDHFxw+NIvlI=
|
||||
github.com/aws/aws-sdk-go v1.44.37/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aws/aws-sdk-go v1.44.111 h1:AcWfOgeedSQ4gQVwcIe6aLxpQNJMloZQyqnr7Dzki+s=
|
||||
github.com/aws/aws-sdk-go v1.44.111/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=
|
||||
|
@ -149,6 +144,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
|||
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=
|
||||
|
@ -166,7 +163,6 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
|
|||
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=
|
||||
|
@ -194,8 +190,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=
|
||||
|
@ -218,7 +214,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
|
|||
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/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
|
@ -250,8 +245,9 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
|
|||
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.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
|
@ -273,6 +269,8 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
|
|||
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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -326,8 +324,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
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/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=
|
||||
|
@ -353,18 +351,14 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||
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/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||
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.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/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
|
||||
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=
|
||||
|
@ -394,7 +388,7 @@ 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=
|
||||
|
@ -410,11 +404,13 @@ github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR3
|
|||
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=
|
||||
|
@ -437,15 +433,15 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
|||
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.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/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E=
|
||||
github.com/hashicorp/vault/api v1.8.1 h1:bMieWIe6dAlqAAPReZO/8zYtXaWUg/21umwqGZpEjCI=
|
||||
github.com/hashicorp/vault/api v1.8.1/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E=
|
||||
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=
|
||||
|
@ -471,8 +467,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=
|
||||
|
@ -488,26 +485,30 @@ 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=
|
||||
|
@ -528,8 +529,9 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
|
|||
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=
|
||||
|
@ -596,8 +598,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
|||
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=
|
||||
|
@ -613,8 +615,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
|
|||
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.18.0 h1:AOR3hhF2ZVE0yfvNPuOaEhEvNMYyIfEBY8EizQpnt7g=
|
||||
github.com/newrelic/go-agent/v3 v3.18.0/go.mod h1:BFJOlbZWRlPTXKYIC1TTTtQKTnYntEJaU0VU507hDc0=
|
||||
github.com/newrelic/go-agent/v3 v3.19.2 h1:gu5Vyp/61gMzXptr+va7+pliKaRkNShoSadYG8dM0IQ=
|
||||
github.com/newrelic/go-agent/v3 v3.19.2/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=
|
||||
|
@ -676,8 +678,9 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
|
|||
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=
|
||||
|
@ -698,17 +701,17 @@ 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=
|
||||
|
@ -733,8 +736,9 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
|
|||
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/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
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=
|
||||
|
@ -742,8 +746,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
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=
|
||||
|
@ -752,8 +757,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
|||
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=
|
||||
|
@ -780,13 +785,13 @@ 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.2 h1:IcPqZ5y7MZNq1+VbYQcKoQEvX80NKRncU1WFCDyY+So=
|
||||
go.step.sm/linkedca v0.19.0-rc.2/go.mod h1:MCZmPIdzElEofZbiw4eyUHayXgFTwa94cNAV34aJ5ew=
|
||||
go.step.sm/crypto v0.21.0 h1:Qwxk5JrqG0Q1t8tOfDM3zKTNECG6J5J24qgWZCRM0Ic=
|
||||
go.step.sm/crypto v0.21.0/go.mod h1:diT2XWIHQy0397UI3i78qCKeLLLp2wu0/DIJI66u/MU=
|
||||
go.step.sm/linkedca v0.19.0-rc.3 h1:3Uu8j187wm7mby+/pz/aQ0wHKRm7w/2AsVPpvcAn4v8=
|
||||
go.step.sm/linkedca v0.19.0-rc.3/go.mod h1:MCZmPIdzElEofZbiw4eyUHayXgFTwa94cNAV34aJ5ew=
|
||||
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=
|
||||
|
@ -818,8 +823,9 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
|
|||
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/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY=
|
||||
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/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=
|
||||
|
@ -902,15 +908,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||
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/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
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=
|
||||
|
@ -927,13 +926,8 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
|
|||
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/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
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=
|
||||
|
@ -945,7 +939,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
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=
|
||||
|
@ -1018,16 +1011,9 @@ golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
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-20220503163025-988cb79eb6c6/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/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=
|
||||
|
@ -1041,8 +1027,8 @@ 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/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -1117,9 +1103,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
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-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
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=
|
||||
|
@ -1154,14 +1137,8 @@ google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3h
|
|||
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.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
|
||||
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
|
||||
google.golang.org/api v0.96.0 h1:F60cuQPJq7K7FzsxMYHAUJSiXh2oKctHxBMbDygxhfM=
|
||||
google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
|
||||
google.golang.org/api v0.100.0 h1:LGUYIrbW9pzYQQ8NWXlaIVkgnfubVBZbMFb9P8TK374=
|
||||
google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
|
||||
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=
|
||||
|
@ -1212,7 +1189,6 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D
|
|||
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-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
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=
|
||||
|
@ -1238,23 +1214,8 @@ google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ6
|
|||
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-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/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-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220929141241-1ce7b20da813 h1:buul04Ikd79A5tP8nGhKEyMfr+/HplsO6nqSUapWZ/M=
|
||||
google.golang.org/genproto v0.0.0-20220929141241-1ce7b20da813/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
|
||||
google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a h1:GH6UPn3ixhWcKDhpnEC55S75cerLPdpp3hrhfKYjZgw=
|
||||
google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
|
||||
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=
|
||||
|
@ -1289,12 +1250,8 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
|
|||
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.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
|
||||
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
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/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
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=
|
||||
|
@ -1310,14 +1267,14 @@ google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX7
|
|||
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/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=
|
||||
|
|
36
pki/helm.go
36
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 }}
|
||||
|
|
232
pki/helm_test.go
Normal file
232
pki/helm_test.go
Normal file
|
@ -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=")
|
||||
}
|
38
pki/pki.go
38
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 {
|
||||
|
@ -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) {
|
||||
|
|
81
pki/testdata/helm/simple.yml
vendored
Normal file
81
pki/testdata/helm/simple.yml
vendored
Normal file
|
@ -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-----
|
||||
|
82
pki/testdata/helm/with-acme.yml
vendored
Normal file
82
pki/testdata/helm/with-acme.yml
vendored
Normal file
|
@ -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-----
|
||||
|
81
pki/testdata/helm/with-admin.yml
vendored
Normal file
81
pki/testdata/helm/with-admin.yml
vendored
Normal file
|
@ -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-----
|
||||
|
81
pki/testdata/helm/with-provisioner.yml
vendored
Normal file
81
pki/testdata/helm/with-provisioner.yml
vendored
Normal file
|
@ -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-----
|
||||
|
105
pki/testdata/helm/with-ssh-and-acme.yml
vendored
Normal file
105
pki/testdata/helm/with-ssh-and-acme.yml
vendored
Normal file
|
@ -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-----
|
||||
|
104
pki/testdata/helm/with-ssh.yml
vendored
Normal file
104
pki/testdata/helm/with-ssh.yml
vendored
Normal file
|
@ -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-----
|
||||
|
|
@ -281,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)
|
||||
|
|
97
webhook/options.go
Normal file
97
webhook/options.go
Normal file
|
@ -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
|
||||
}
|
||||
}
|
116
webhook/options_test.go
Normal file
116
webhook/options_test.go
Normal file
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
71
webhook/types.go
Normal file
71
webhook/types.go
Normal file
|
@ -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"`
|
||||
}
|
Loading…
Reference in a new issue