diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml new file mode 100644 index 00000000..8e9248e0 --- /dev/null +++ b/.github/workflows/actionlint.yml @@ -0,0 +1,17 @@ +name: Lint GitHub Actions workflows +on: + push: + workflow_call: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: write + pull-requests: write + +jobs: + actionlint: + uses: smallstep/workflows/.github/workflows/actionlint.yml@main + secrets: inherit diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 44908aae..c0b39e0c 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -6,17 +6,6 @@ permissions: pull-requests: write jobs: - dependabot: - runs-on: ubuntu-latest - if: ${{ github.actor == 'dependabot[bot]' }} - steps: - - name: Dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v2.0.0 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Enable auto-merge for Dependabot PRs - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + dependabot-auto-merge: + uses: smallstep/workflows/.github/workflows/dependabot-auto-merge.yml@main + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab821066..43dfc6c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,12 +45,12 @@ jobs: echo "DOCKER_TAGS_HSM=${{ env.DOCKER_TAGS_HSM }},${{ env.DOCKER_IMAGE }}:hsm" >> "${GITHUB_ENV}" - name: Create Release id: create_release - uses: actions/create-release@v1 + uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} + tag_name: ${{ github.ref_name }} + name: Release ${{ github.ref_name }} draft: false prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 41747192..54368e97 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,6 +1,6 @@ -# This is an example .goreleaser.yml file with some sane defaults. -# Make sure to check the documentation at http://goreleaser.com +# Documentation: https://goreleaser.com/customization/ project_name: step-ca +version: 2 before: hooks: @@ -268,7 +268,7 @@ winget: # Release notes URL. # # Templates: allowed - release_notes_url: "https://github.com/smallstep/certificates/releases/tag/{{.Version}}" + release_notes_url: "https://github.com/smallstep/certificates/releases/tag/{{ .Tag }}" # Create the PR - for testing skip_upload: auto @@ -283,7 +283,7 @@ winget: repository: owner: smallstep name: winget-pkgs - branch: step + branch: "step-ca-{{.Version}}" # Optionally a token can be provided, if it differs from the token # provided to GoReleaser diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9e2029..62a9ff30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,94 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. --- +## [0.27.2] - 2024-07-18 + +### Added + +- `--console` option to default step-ssh config (smallstep/certificates#1931) + + +## [0.27.1] - 2024-07-12 + +### Changed + +- Enable use of strict FQDN with a flag (smallstep/certificates#1926) + - This reverses a change in 0.27.0 that required the use of strict FQDNs (smallstep/certificate#1910) + + +## [0.27.0] - 2024-07-11 + +### Added + +- Support for validity windows in templates (smallstep/certificates#1903) +- Create identity certificate with host URI when using any provisioner (smallstep/certificates#1922) + +### Changed + +- Do strict DNS lookup on ACME (smallstep/certificates#1910) + +### Fixed + +- Handle bad attestation object in deviceAttest01 validation (smallstep/certificates#1913) + + +## [0.26.2] - 2024-06-13 + +### Added + +- Add provisionerID to ACME accounts (smallstep/certificates#1830) +- Enable verifying ACME provisioner using provisionerID if available (smallstep/certificates#1844) +- Add methods to Authority to get intermediate certificates (smallstep/certificates#1848) +- Add GetX509Signer method (smallstep/certificates#1850) + +### Changed + +- Make ISErrNotFound more flexible (smallstep/certificates#1819) +- Log errors using slog.Logger (smallstep/certificates#1849) +- Update hardcoded AWS certificates (smallstep/certificates#1881) + + +## [0.26.1] - 2024-04-22 + +### Added + +- Allow configuration of a custom SCEP key manager (smallstep/certificates#1797) + +### Fixed + +- id-scep-failInfoText OID (smallstep/certificates#1794) +- CA startup with Vault RA configuration (smallstep/certificates#1803) + + +## [0.26.0] - 2024-03-28 + +### Added + +- [TPM KMS](https://github.com/smallstep/crypto/tree/master/kms/tpmkms) support for CA keys (smallstep/certificates#1772) +- Propagation of HTTP request identifier using X-Request-Id header (smallstep/certificates#1743, smallstep/certificates#1542) +- Expires header in CRL response (smallstep/certificates#1708) +- Support for providing TLS configuration programmatically (smallstep/certificates#1685) +- Support for providing external CAS implementation (smallstep/certificates#1684) +- AWS `ca-west-1` identity document root certificate (smallstep/certificates#1715) +- [COSE RS1](https://www.rfc-editor.org/rfc/rfc8812.html#section-2) as a supported algorithm with ACME `device-attest-01` challenge (smallstep/certificates#1663) + +### Changed + +- In an RA setup, let the CA decide the RA certificate lifetime (smallstep/certificates#1764) +- Use Debian Bookworm in Docker containers (smallstep/certificates#1615) +- Error message for CSR validation (smallstep/certificates#1665) +- Updated dependencies + +### Fixed + +- Stop CA when any of the required servers fails to start (smallstep/certificates#1751). Before the fix, the CA would continue running and only log the server failure when stopped. +- Configuration loading errors when not using context were not returned. Fixed in [cli-utils/109](https://github.com/smallstep/cli-utils/pull/109). +- HTTP_PROXY and HTTPS_PROXY support for ACME validation client (smallstep/certificates#1658). + +### Security + +- Upgrade to using cosign v2 for signing artifacts + ## [0.25.1] - 2023-11-28 ### Added diff --git a/Makefile b/Makefile index 630b54b9..8169a6a5 100644 --- a/Makefile +++ b/Makefile @@ -147,7 +147,7 @@ lint: # Install ######################################### -INSTALL_PREFIX?=/usr/ +INSTALL_PREFIX?=/usr/local/ install: $(PREFIX)bin/$(BINNAME) $Q install -D $(PREFIX)bin/$(BINNAME) $(DESTDIR)$(INSTALL_PREFIX)bin/$(BINNAME) diff --git a/acme/account.go b/acme/account.go index 38cca218..246e031b 100644 --- a/acme/account.go +++ b/acme/account.go @@ -21,6 +21,7 @@ type Account struct { OrdersURL string `json:"orders"` ExternalAccountBinding interface{} `json:"externalAccountBinding,omitempty"` LocationPrefix string `json:"-"` + ProvisionerID string `json:"-"` ProvisionerName string `json:"-"` } diff --git a/acme/api/account.go b/acme/api/account.go index 25d923c7..3114dcb3 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -82,23 +82,23 @@ func NewAccount(w http.ResponseWriter, r *http.Request) { payload, err := payloadFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } var nar NewAccountRequest if err := json.Unmarshal(payload.value, &nar); err != nil { - render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, + render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "failed to unmarshal new-account request payload")) return } if err := nar.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } prov, err := acmeProvisionerFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -108,26 +108,26 @@ func NewAccount(w http.ResponseWriter, r *http.Request) { var acmeErr *acme.Error if !errors.As(err, &acmeErr) || acmeErr.Status != http.StatusBadRequest { // Something went wrong ... - render.Error(w, err) + render.Error(w, r, err) return } // Account does not exist // if nar.OnlyReturnExisting { - render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType, + render.Error(w, r, acme.NewError(acme.ErrorAccountDoesNotExistType, "account does not exist")) return } jwk, err := jwkFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } eak, err := validateExternalAccountBinding(ctx, &nar) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -136,20 +136,21 @@ func NewAccount(w http.ResponseWriter, r *http.Request) { Contact: nar.Contact, Status: acme.StatusValid, LocationPrefix: getAccountLocationPath(ctx, linker, ""), - ProvisionerName: prov.GetName(), + ProvisionerID: prov.ID, + ProvisionerName: prov.Name, } if err := db.CreateAccount(ctx, acc); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error creating account")) + render.Error(w, r, acme.WrapErrorISE(err, "error creating account")) return } if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response if err := eak.BindTo(acc); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if err := db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error updating external account binding key")) + render.Error(w, r, acme.WrapErrorISE(err, "error updating external account binding key")) return } acc.ExternalAccountBinding = nar.ExternalAccountBinding @@ -162,7 +163,7 @@ func NewAccount(w http.ResponseWriter, r *http.Request) { linker.LinkAccount(ctx, acc) w.Header().Set("Location", getAccountLocationPath(ctx, linker, acc.ID)) - render.JSONStatus(w, acc, httpStatus) + render.JSONStatus(w, r, acc, httpStatus) } // GetOrUpdateAccount is the api for updating an ACME account. @@ -173,12 +174,12 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) { acc, err := accountFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } payload, err := payloadFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -187,12 +188,12 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) { if !payload.isPostAsGet { var uar UpdateAccountRequest if err := json.Unmarshal(payload.value, &uar); err != nil { - render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, + render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "failed to unmarshal new-account request payload")) return } if err := uar.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if len(uar.Status) > 0 || len(uar.Contact) > 0 { @@ -203,7 +204,7 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) { } if err := db.UpdateAccount(ctx, acc); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error updating account")) + render.Error(w, r, acme.WrapErrorISE(err, "error updating account")) return } } @@ -212,7 +213,7 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) { linker.LinkAccount(ctx, acc) w.Header().Set("Location", linker.GetLink(ctx, acme.AccountLinkType, acc.ID)) - render.JSON(w, acc) + render.JSON(w, r, acc) } func logOrdersByAccount(w http.ResponseWriter, oids []string) { @@ -232,23 +233,23 @@ func GetOrdersByAccountID(w http.ResponseWriter, r *http.Request) { acc, err := accountFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } accID := chi.URLParam(r, "accID") if acc.ID != accID { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID)) + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account ID '%s' does not match url param '%s'", acc.ID, accID)) return } orders, err := db.GetOrdersByAccountID(ctx, acc.ID) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } linker.LinkOrdersByAccountID(ctx, orders) - render.JSON(w, orders) + render.JSON(w, r, orders) logOrdersByAccount(w, orders) } diff --git a/acme/api/account_test.go b/acme/api/account_test.go index 7d799c88..b0fa6e2c 100644 --- a/acme/api/account_test.go +++ b/acme/api/account_test.go @@ -14,6 +14,7 @@ import ( "time" "github.com/go-chi/chi/v5" + "github.com/google/uuid" "github.com/pkg/errors" "go.step.sm/crypto/jose" @@ -66,6 +67,19 @@ func newProv() acme.Provisioner { return p } +func newProvWithID() acme.Provisioner { + // Initialize provisioners + p := &provisioner.ACME{ + ID: uuid.NewString(), + Type: "ACME", + Name: "test@acme-provisioner.com", + } + if err := p.Init(provisioner.Config{Claims: globalProvisionerClaims}); err != nil { + fmt.Printf("%v", err) + } + return p +} + func newProvWithOptions(options *provisioner.Options) acme.Provisioner { // Initialize provisioners p := &provisioner.ACME{ diff --git a/acme/api/handler.go b/acme/api/handler.go index d2940f49..0722bd9b 100644 --- a/acme/api/handler.go +++ b/acme/api/handler.go @@ -223,13 +223,13 @@ func GetDirectory(w http.ResponseWriter, r *http.Request) { ctx := r.Context() acmeProv, err := acmeProvisionerFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } linker := acme.MustLinkerFromContext(ctx) - render.JSON(w, &Directory{ + render.JSON(w, r, &Directory{ NewNonce: linker.GetLink(ctx, acme.NewNonceLinkType), NewAccount: linker.GetLink(ctx, acme.NewAccountLinkType), NewOrder: linker.GetLink(ctx, acme.NewOrderLinkType), @@ -273,8 +273,8 @@ func shouldAddMetaObject(p *provisioner.ACME) bool { // NotImplemented returns a 501 and is generally a placeholder for functionality which // MAY be added at some point in the future but is not in any way a guarantee of such. -func NotImplemented(w http.ResponseWriter, _ *http.Request) { - render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented")) +func NotImplemented(w http.ResponseWriter, r *http.Request) { + render.Error(w, r, acme.NewError(acme.ErrorNotImplementedType, "this API is not implemented")) } // GetAuthorization ACME api for retrieving an Authz. @@ -285,28 +285,28 @@ func GetAuthorization(w http.ResponseWriter, r *http.Request) { acc, err := accountFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } az, err := db.GetAuthorization(ctx, chi.URLParam(r, "authzID")) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error retrieving authorization")) + render.Error(w, r, acme.WrapErrorISE(err, "error retrieving authorization")) return } if acc.ID != az.AccountID { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account '%s' does not own authorization '%s'", acc.ID, az.ID)) return } if err = az.UpdateStatus(ctx, db); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error updating authorization status")) + render.Error(w, r, acme.WrapErrorISE(err, "error updating authorization status")) return } linker.LinkAuthorization(ctx, az) w.Header().Set("Location", linker.GetLink(ctx, acme.AuthzLinkType, az.ID)) - render.JSON(w, az) + render.JSON(w, r, az) } // GetChallenge ACME api for retrieving a Challenge. @@ -317,13 +317,13 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) { acc, err := accountFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } payload, err := payloadFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -336,22 +336,22 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) { azID := chi.URLParam(r, "authzID") ch, err := db.GetChallenge(ctx, chi.URLParam(r, "chID"), azID) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error retrieving challenge")) + render.Error(w, r, acme.WrapErrorISE(err, "error retrieving challenge")) return } ch.AuthorizationID = azID if acc.ID != ch.AccountID { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account '%s' does not own challenge '%s'", acc.ID, ch.ID)) return } jwk, err := jwkFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if err = ch.Validate(ctx, db, jwk, payload.value); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error validating challenge")) + render.Error(w, r, acme.WrapErrorISE(err, "error validating challenge")) return } @@ -359,7 +359,7 @@ func GetChallenge(w http.ResponseWriter, r *http.Request) { w.Header().Add("Link", link(linker.GetLink(ctx, acme.AuthzLinkType, azID), "up")) w.Header().Set("Location", linker.GetLink(ctx, acme.ChallengeLinkType, azID, ch.ID)) - render.JSON(w, ch) + render.JSON(w, r, ch) } // GetCertificate ACME api for retrieving a Certificate. @@ -369,18 +369,18 @@ func GetCertificate(w http.ResponseWriter, r *http.Request) { acc, err := accountFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } certID := chi.URLParam(r, "certID") cert, err := db.GetCertificate(ctx, certID) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error retrieving certificate")) + render.Error(w, r, acme.WrapErrorISE(err, "error retrieving certificate")) return } if cert.AccountID != acc.ID { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account '%s' does not own certificate '%s'", acc.ID, certID)) return } diff --git a/acme/api/middleware.go b/acme/api/middleware.go index afccca70..628da7ed 100644 --- a/acme/api/middleware.go +++ b/acme/api/middleware.go @@ -36,7 +36,7 @@ func addNonce(next nextHTTP) nextHTTP { db := acme.MustDatabaseFromContext(r.Context()) nonce, err := db.CreateNonce(r.Context()) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } w.Header().Set("Replay-Nonce", string(nonce)) @@ -64,7 +64,7 @@ func verifyContentType(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { p, err := provisionerFromContext(r.Context()) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -88,7 +88,7 @@ func verifyContentType(next nextHTTP) nextHTTP { return } } - render.Error(w, acme.NewError(acme.ErrorMalformedType, + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "expected content-type to be in %s, but got %s", expected, ct)) } } @@ -98,12 +98,12 @@ func parseJWS(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "failed to read request body")) + render.Error(w, r, acme.WrapErrorISE(err, "failed to read request body")) return } jws, err := jose.ParseJWS(string(body)) if err != nil { - render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "failed to parse JWS from request body")) + render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "failed to parse JWS from request body")) return } ctx := context.WithValue(r.Context(), jwsContextKey, jws) @@ -133,15 +133,15 @@ func validateJWS(next nextHTTP) nextHTTP { jws, err := jwsFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if len(jws.Signatures) == 0 { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "request body does not contain a signature")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "request body does not contain a signature")) return } if len(jws.Signatures) > 1 { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "request body contains more than one signature")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "request body contains more than one signature")) return } @@ -152,7 +152,7 @@ func validateJWS(next nextHTTP) nextHTTP { uh.Algorithm != "" || uh.Nonce != "" || len(uh.ExtraHeaders) > 0 { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "unprotected header must not be used")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "unprotected header must not be used")) return } hdr := sig.Protected @@ -162,13 +162,13 @@ func validateJWS(next nextHTTP) nextHTTP { switch k := hdr.JSONWebKey.Key.(type) { case *rsa.PublicKey: if k.Size() < keyutil.MinRSAKeyBytes { - render.Error(w, acme.NewError(acme.ErrorMalformedType, + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "rsa keys must be at least %d bits (%d bytes) in size", 8*keyutil.MinRSAKeyBytes, keyutil.MinRSAKeyBytes)) return } default: - render.Error(w, acme.NewError(acme.ErrorMalformedType, + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jws key type and algorithm do not match")) return } @@ -176,35 +176,35 @@ func validateJWS(next nextHTTP) nextHTTP { case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA: // we good default: - render.Error(w, acme.NewError(acme.ErrorBadSignatureAlgorithmType, "unsuitable algorithm: %s", hdr.Algorithm)) + render.Error(w, r, acme.NewError(acme.ErrorBadSignatureAlgorithmType, "unsuitable algorithm: %s", hdr.Algorithm)) return } // Check the validity/freshness of the Nonce. if err := db.DeleteNonce(ctx, acme.Nonce(hdr.Nonce)); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } // Check that the JWS url matches the requested url. jwsURL, ok := hdr.ExtraHeaders["url"].(string) if !ok { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "jws missing url protected header")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jws missing url protected header")) return } reqURL := &url.URL{Scheme: "https", Host: r.Host, Path: r.URL.Path} if jwsURL != reqURL.String() { - render.Error(w, acme.NewError(acme.ErrorMalformedType, + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "url header in JWS (%s) does not match request url (%s)", jwsURL, reqURL)) return } if hdr.JSONWebKey != nil && hdr.KeyID != "" { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "jwk and kid are mutually exclusive")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jwk and kid are mutually exclusive")) return } if hdr.JSONWebKey == nil && hdr.KeyID == "" { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "either jwk or kid must be defined in jws protected header")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "either jwk or kid must be defined in jws protected header")) return } next(w, r) @@ -221,23 +221,23 @@ func extractJWK(next nextHTTP) nextHTTP { jws, err := jwsFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } jwk := jws.Signatures[0].Protected.JSONWebKey if jwk == nil { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "jwk expected in protected header")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "jwk expected in protected header")) return } if !jwk.Valid() { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "invalid jwk in protected header")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "invalid jwk in protected header")) return } // Overwrite KeyID with the JWK thumbprint. jwk.KeyID, err = acme.KeyToID(jwk) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error getting KeyID from JWK")) + render.Error(w, r, acme.WrapErrorISE(err, "error getting KeyID from JWK")) return } @@ -247,15 +247,15 @@ func extractJWK(next nextHTTP) nextHTTP { // Get Account OR continue to generate a new one OR continue Revoke with certificate private key acc, err := db.GetAccountByKeyID(ctx, jwk.KeyID) switch { - case errors.Is(err, acme.ErrNotFound): + case acme.IsErrNotFound(err): // For NewAccount and Revoke requests ... break case err != nil: - render.Error(w, err) + render.Error(w, r, err) return default: if !acc.IsValid() { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active")) + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account is not active")) return } ctx = context.WithValue(ctx, accContextKey, acc) @@ -274,11 +274,11 @@ func checkPrerequisites(next nextHTTP) nextHTTP { if ok { ok, err := checkFunc(ctx) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error checking acme provisioner prerequisites")) + render.Error(w, r, acme.WrapErrorISE(err, "error checking acme provisioner prerequisites")) return } if !ok { - render.Error(w, acme.NewError(acme.ErrorNotImplementedType, "acme provisioner configuration lacks prerequisites")) + render.Error(w, r, acme.NewError(acme.ErrorNotImplementedType, "acme provisioner configuration lacks prerequisites")) return } } @@ -296,13 +296,13 @@ func lookupJWK(next nextHTTP) nextHTTP { jws, err := jwsFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } kid := jws.Signatures[0].Protected.KeyID if kid == "" { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "signature missing 'kid'")) return } @@ -310,14 +310,14 @@ func lookupJWK(next nextHTTP) nextHTTP { acc, err := db.GetAccount(ctx, accID) switch { case acme.IsErrNotFound(err): - render.Error(w, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID)) + render.Error(w, r, acme.NewError(acme.ErrorAccountDoesNotExistType, "account with ID '%s' not found", accID)) return case err != nil: - render.Error(w, err) + render.Error(w, r, err) return default: if !acc.IsValid() { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, "account is not active")) + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account is not active")) return } @@ -325,7 +325,7 @@ func lookupJWK(next nextHTTP) nextHTTP { if kid != storedLocation { // ACME accounts should have a stored location equivalent to the // kid in the ACME request. - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "kid does not match stored account location; expected %s, but got %s", storedLocation, kid)) return @@ -334,14 +334,16 @@ func lookupJWK(next nextHTTP) nextHTTP { // Verify that the provisioner with which the account was created // matches the provisioner in the request URL. reqProv := acme.MustProvisionerFromContext(ctx) - reqProvName := reqProv.GetName() - accProvName := acc.ProvisionerName - if reqProvName != accProvName { - // Provisioner in the URL must match the provisioner with - // which the account was created. - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, + switch { + case acc.ProvisionerID == "" && acc.ProvisionerName != reqProv.GetName(): + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s", - accProvName, reqProvName)) + acc.ProvisionerName, reqProv.GetName())) + return + case acc.ProvisionerID != "" && acc.ProvisionerID != reqProv.GetID(): + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, + "account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s", + acc.ProvisionerID, reqProv.GetID())) return } } else { @@ -353,7 +355,7 @@ func lookupJWK(next nextHTTP) nextHTTP { linker := acme.MustLinkerFromContext(ctx) kidPrefix := linker.GetLink(ctx, acme.AccountLinkType, "") if !strings.HasPrefix(kid, kidPrefix) { - render.Error(w, acme.NewError(acme.ErrorMalformedType, + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "kid does not have required prefix; expected %s, but got %s", kidPrefix, kid)) return @@ -374,7 +376,7 @@ func extractOrLookupJWK(next nextHTTP) nextHTTP { ctx := r.Context() jws, err := jwsFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -410,16 +412,16 @@ func verifyAndExtractJWSPayload(next nextHTTP) nextHTTP { ctx := r.Context() jws, err := jwsFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } jwk, err := jwkFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if jwk.Algorithm != "" && jwk.Algorithm != jws.Signatures[0].Protected.Algorithm { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "verifier and signature algorithm do not match")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "verifier and signature algorithm do not match")) return } @@ -428,11 +430,11 @@ func verifyAndExtractJWSPayload(next nextHTTP) nextHTTP { case errors.Is(err, jose.ErrCryptoFailure): payload, err = retryVerificationWithPatchedSignatures(jws, jwk) if err != nil { - render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws with patched signature(s)")) + render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws with patched signature(s)")) return } case err != nil: - render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws")) + render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error verifying jws")) return } @@ -549,11 +551,11 @@ func isPostAsGet(next nextHTTP) nextHTTP { return func(w http.ResponseWriter, r *http.Request) { payload, err := payloadFromContext(r.Context()) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if !payload.isPostAsGet { - render.Error(w, acme.NewError(acme.ErrorMalformedType, "expected POST-as-GET")) + render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "expected POST-as-GET")) return } next(w, r) diff --git a/acme/api/middleware_test.go b/acme/api/middleware_test.go index 14320ec2..7dcbb644 100644 --- a/acme/api/middleware_test.go +++ b/acme/api/middleware_test.go @@ -15,6 +15,7 @@ import ( "strings" "testing" + "github.com/google/uuid" "github.com/smallstep/assert" "github.com/smallstep/certificates/acme" tassert "github.com/stretchr/testify/assert" @@ -831,8 +832,37 @@ func TestHandler_lookupJWK(t *testing.T) { }, statusCode: http.StatusUnauthorized, err: acme.NewError(acme.ErrorUnauthorizedType, - "account provisioner does not match requested provisioner; account provisioner = %s, reqested provisioner = %s", - prov.GetName(), "other"), + "account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s", + "other", prov.GetName()), + } + }, + "fail/account-with-location-prefix/bad-provisioner-id": func(t *testing.T) test { + p := newProvWithID() + acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerID: uuid.NewString()} + ctx := acme.NewProvisionerContext(context.Background(), p) + ctx = context.WithValue(ctx, jwsContextKey, parsedJWS) + return test{ + linker: acme.NewLinker("test.ca.smallstep.com", "acme"), + db: &acme.MockDB{ + MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) { + assert.Equals(t, id, accID) + return acc, nil + }, + }, + ctx: ctx, + next: func(w http.ResponseWriter, r *http.Request) { + _acc, err := accountFromContext(r.Context()) + assert.FatalError(t, err) + assert.Equals(t, _acc, acc) + _jwk, err := jwkFromContext(r.Context()) + assert.FatalError(t, err) + assert.Equals(t, _jwk, jwk) + w.Write(testBody) + }, + statusCode: http.StatusUnauthorized, + err: acme.NewError(acme.ErrorUnauthorizedType, + "account provisioner does not match requested provisioner; account provisioner = %s, requested provisioner = %s", + acc.ProvisionerID, p.GetID()), } }, "ok/account-with-location-prefix": func(t *testing.T) test { @@ -885,6 +915,32 @@ func TestHandler_lookupJWK(t *testing.T) { statusCode: 200, } }, + "ok/account-with-provisioner-id": func(t *testing.T) test { + p := newProvWithID() + acc := &acme.Account{LocationPrefix: prefix + accID, Status: "valid", Key: jwk, ProvisionerID: p.GetID()} + ctx := acme.NewProvisionerContext(context.Background(), p) + ctx = context.WithValue(ctx, jwsContextKey, parsedJWS) + return test{ + linker: acme.NewLinker("test.ca.smallstep.com", "acme"), + db: &acme.MockDB{ + MockGetAccount: func(ctx context.Context, id string) (*acme.Account, error) { + assert.Equals(t, id, accID) + return acc, nil + }, + }, + ctx: ctx, + next: func(w http.ResponseWriter, r *http.Request) { + _acc, err := accountFromContext(r.Context()) + assert.FatalError(t, err) + assert.Equals(t, _acc, acc) + _jwk, err := jwkFromContext(r.Context()) + assert.FatalError(t, err) + assert.Equals(t, _jwk, jwk) + w.Write(testBody) + }, + statusCode: 200, + } + }, } for name, run := range tests { tc := run(t) diff --git a/acme/api/order.go b/acme/api/order.go index b207f87c..77e32fdb 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -99,29 +99,29 @@ func NewOrder(w http.ResponseWriter, r *http.Request) { acc, err := accountFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } prov, err := provisionerFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } payload, err := payloadFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } var nor NewOrderRequest if err := json.Unmarshal(payload.value, &nor); err != nil { - render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, + render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "failed to unmarshal new-order request payload")) return } if err := nor.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -130,39 +130,39 @@ func NewOrder(w http.ResponseWriter, r *http.Request) { acmeProv, err := acmeProvisionerFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } var eak *acme.ExternalAccountKey if acmeProv.RequireEAB { if eak, err = db.GetExternalAccountKeyByAccountID(ctx, prov.GetID(), acc.ID); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error retrieving external account binding key")) + render.Error(w, r, acme.WrapErrorISE(err, "error retrieving external account binding key")) return } } acmePolicy, err := newACMEPolicyEngine(eak) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error creating ACME policy engine")) + render.Error(w, r, acme.WrapErrorISE(err, "error creating ACME policy engine")) return } for _, identifier := range nor.Identifiers { // evaluate the ACME account level policy if err = isIdentifierAllowed(acmePolicy, identifier); err != nil { - render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized")) + render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized")) return } // evaluate the provisioner level policy orderIdentifier := provisioner.ACMEIdentifier{Type: provisioner.ACMEIdentifierType(identifier.Type), Value: identifier.Value} if err = prov.AuthorizeOrderIdentifier(ctx, orderIdentifier); err != nil { - render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized")) + render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized")) return } // evaluate the authority level policy if err = ca.AreSANsAllowed(ctx, []string{identifier.Value}); err != nil { - render.Error(w, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized")) + render.Error(w, r, acme.WrapError(acme.ErrorRejectedIdentifierType, err, "not authorized")) return } } @@ -188,7 +188,7 @@ func NewOrder(w http.ResponseWriter, r *http.Request) { Status: acme.StatusPending, } if err := newAuthorization(ctx, az); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } o.AuthorizationIDs[i] = az.ID @@ -207,14 +207,14 @@ func NewOrder(w http.ResponseWriter, r *http.Request) { } if err := db.CreateOrder(ctx, o); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error creating order")) + render.Error(w, r, acme.WrapErrorISE(err, "error creating order")) return } linker.LinkOrder(ctx, o) w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID)) - render.JSONStatus(w, o, http.StatusCreated) + render.JSONStatus(w, r, o, http.StatusCreated) } func isIdentifierAllowed(acmePolicy policy.X509Policy, identifier acme.Identifier) error { @@ -226,6 +226,7 @@ func isIdentifierAllowed(acmePolicy policy.X509Policy, identifier acme.Identifie func newACMEPolicyEngine(eak *acme.ExternalAccountKey) (policy.X509Policy, error) { if eak == nil { + //nolint:nilnil,nolintlint // expected values return nil, nil } return policy.NewX509PolicyEngine(eak.Policy) @@ -288,39 +289,39 @@ func GetOrder(w http.ResponseWriter, r *http.Request) { acc, err := accountFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } prov, err := provisionerFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID")) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error retrieving order")) + render.Error(w, r, acme.WrapErrorISE(err, "error retrieving order")) return } if acc.ID != o.AccountID { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account '%s' does not own order '%s'", acc.ID, o.ID)) return } if prov.GetID() != o.ProvisionerID { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "provisioner '%s' does not own order '%s'", prov.GetID(), o.ID)) return } if err = o.UpdateStatus(ctx, db); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error updating order status")) + render.Error(w, r, acme.WrapErrorISE(err, "error updating order status")) return } linker.LinkOrder(ctx, o) w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID)) - render.JSON(w, o) + render.JSON(w, r, o) } // FinalizeOrder attempts to finalize an order and create a certificate. @@ -331,56 +332,56 @@ func FinalizeOrder(w http.ResponseWriter, r *http.Request) { acc, err := accountFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } prov, err := provisionerFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } payload, err := payloadFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } var fr FinalizeRequest if err := json.Unmarshal(payload.value, &fr); err != nil { - render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, + render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "failed to unmarshal finalize-order request payload")) return } if err := fr.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } o, err := db.GetOrder(ctx, chi.URLParam(r, "ordID")) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error retrieving order")) + render.Error(w, r, acme.WrapErrorISE(err, "error retrieving order")) return } if acc.ID != o.AccountID { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "account '%s' does not own order '%s'", acc.ID, o.ID)) return } if prov.GetID() != o.ProvisionerID { - render.Error(w, acme.NewError(acme.ErrorUnauthorizedType, + render.Error(w, r, acme.NewError(acme.ErrorUnauthorizedType, "provisioner '%s' does not own order '%s'", prov.GetID(), o.ID)) return } ca := mustAuthority(ctx) if err = o.Finalize(ctx, db, fr.csr, ca, prov); err != nil { - render.Error(w, acme.WrapErrorISE(err, "error finalizing order")) + render.Error(w, r, acme.WrapErrorISE(err, "error finalizing order")) return } linker.LinkOrder(ctx, o) w.Header().Set("Location", linker.GetLink(ctx, acme.OrderLinkType, o.ID)) - render.JSON(w, o) + render.JSON(w, r, o) } // challengeTypes determines the types of challenges that should be used diff --git a/acme/api/revoke.go b/acme/api/revoke.go index 270a9fbb..c97d54c1 100644 --- a/acme/api/revoke.go +++ b/acme/api/revoke.go @@ -33,65 +33,65 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) { jws, err := jwsFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } prov, err := provisionerFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } payload, err := payloadFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } var p revokePayload err = json.Unmarshal(payload.value, &p) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error unmarshaling payload")) + render.Error(w, r, acme.WrapErrorISE(err, "error unmarshaling payload")) return } certBytes, err := base64.RawURLEncoding.DecodeString(p.Certificate) if err != nil { // in this case the most likely cause is a client that didn't properly encode the certificate - render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error base64url decoding payload certificate property")) + render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error base64url decoding payload certificate property")) return } certToBeRevoked, err := x509.ParseCertificate(certBytes) if err != nil { // in this case a client may have encoded something different than a certificate - render.Error(w, acme.WrapError(acme.ErrorMalformedType, err, "error parsing certificate")) + render.Error(w, r, acme.WrapError(acme.ErrorMalformedType, err, "error parsing certificate")) return } serial := certToBeRevoked.SerialNumber.String() dbCert, err := db.GetCertificateBySerial(ctx, serial) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error retrieving certificate by serial")) + render.Error(w, r, acme.WrapErrorISE(err, "error retrieving certificate by serial")) return } if !bytes.Equal(dbCert.Leaf.Raw, certToBeRevoked.Raw) { // this should never happen - render.Error(w, acme.NewErrorISE("certificate raw bytes are not equal")) + render.Error(w, r, acme.NewErrorISE("certificate raw bytes are not equal")) return } if shouldCheckAccountFrom(jws) { account, err := accountFromContext(ctx) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } acmeErr := isAccountAuthorized(ctx, dbCert, certToBeRevoked, account) if acmeErr != nil { - render.Error(w, acmeErr) + render.Error(w, r, acmeErr) return } } else { @@ -100,7 +100,7 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) { _, err := jws.Verify(certToBeRevoked.PublicKey) if err != nil { // TODO(hs): possible to determine an error vs. unauthorized and thus provide an ISE vs. Unauthorized? - render.Error(w, wrapUnauthorizedError(certToBeRevoked, nil, "verification of jws using certificate public key failed", err)) + render.Error(w, r, wrapUnauthorizedError(certToBeRevoked, nil, "verification of jws using certificate public key failed", err)) return } } @@ -108,19 +108,19 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) { ca := mustAuthority(ctx) hasBeenRevokedBefore, err := ca.IsRevoked(serial) if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error retrieving revocation status of certificate")) + render.Error(w, r, acme.WrapErrorISE(err, "error retrieving revocation status of certificate")) return } if hasBeenRevokedBefore { - render.Error(w, acme.NewError(acme.ErrorAlreadyRevokedType, "certificate was already revoked")) + render.Error(w, r, acme.NewError(acme.ErrorAlreadyRevokedType, "certificate was already revoked")) return } reasonCode := p.ReasonCode acmeErr := validateReasonCode(reasonCode) if acmeErr != nil { - render.Error(w, acmeErr) + render.Error(w, r, acmeErr) return } @@ -128,14 +128,14 @@ func RevokeCert(w http.ResponseWriter, r *http.Request) { ctx = provisioner.NewContextWithMethod(ctx, provisioner.RevokeMethod) err = prov.AuthorizeRevoke(ctx, "") if err != nil { - render.Error(w, acme.WrapErrorISE(err, "error authorizing revocation on provisioner")) + render.Error(w, r, acme.WrapErrorISE(err, "error authorizing revocation on provisioner")) return } options := revokeOptions(serial, certToBeRevoked, reasonCode) err = ca.Revoke(ctx, options) if err != nil { - render.Error(w, wrapRevokeErr(err)) + render.Error(w, r, wrapRevokeErr(err)) return } diff --git a/acme/challenge.go b/acme/challenge.go index 995981ab..c30d00df 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -1,6 +1,7 @@ package acme import ( + "bytes" "context" "crypto" "crypto/ecdsa" @@ -62,6 +63,11 @@ var ( // // This variable can be used for testing purposes. InsecurePortTLSALPN01 int + + // StrictFQDN allows to enforce a fully qualified domain name in the DNS + // resolution. By default it allows domain resolution using a search list + // defined in the resolv.conf or similar configuration. + StrictFQDN bool ) // Challenge represents an ACME response Challenge type. @@ -110,16 +116,19 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey, } func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey) error { - u := &url.URL{Scheme: "http", Host: http01ChallengeHost(ch.Value), Path: fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Token)} + u := &url.URL{Scheme: "http", Host: ch.Value, Path: fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Token)} + challengeURL := &url.URL{Scheme: "http", Host: http01ChallengeHost(ch.Value), Path: fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Token)} // Append insecure port if set. // Only used for testing purposes. if InsecurePortHTTP01 != 0 { - u.Host += ":" + strconv.Itoa(InsecurePortHTTP01) + insecurePort := strconv.Itoa(InsecurePortHTTP01) + u.Host += ":" + insecurePort + challengeURL.Host += ":" + insecurePort } vc := MustClientFromContext(ctx) - resp, err := vc.Get(u.String()) + resp, err := vc.Get(challengeURL.String()) if err != nil { return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, err, "error doing http GET for url %s", u)) @@ -157,15 +166,42 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb return nil } +// rootedName adds a trailing "." to a given domain name. +func rootedName(name string) string { + if StrictFQDN { + if name == "" || name[len(name)-1] != '.' { + return name + "." + } + } + return name +} + // http01ChallengeHost checks if a Challenge value is an IPv6 address // and adds square brackets if that's the case, so that it can be used // as a hostname. Returns the original Challenge value as the host to // use in other cases. func http01ChallengeHost(value string) string { - if ip := net.ParseIP(value); ip != nil && ip.To4() == nil { - value = "[" + value + "]" + if ip := net.ParseIP(value); ip != nil { + if ip.To4() == nil { + value = "[" + value + "]" + } + return value } - return value + return rootedName(value) +} + +// tlsAlpn01ChallengeHost returns the rooted DNS used on TLS-ALPN-01 +// validations. +func tlsAlpn01ChallengeHost(name string) string { + if ip := net.ParseIP(name); ip != nil { + return name + } + return rootedName(name) +} + +// dns01ChallengeHost returns the TXT record used in DNS-01 validations. +func dns01ChallengeHost(domain string) string { + return "_acme-challenge." + rootedName(domain) } func tlsAlert(err error) uint8 { @@ -190,13 +226,12 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON InsecureSkipVerify: true, //nolint:gosec // we expect a self-signed challenge certificate } - var hostPort string - // Allow to change TLS port for testing purposes. + hostPort := tlsAlpn01ChallengeHost(ch.Value) if port := InsecurePortTLSALPN01; port == 0 { - hostPort = net.JoinHostPort(ch.Value, "443") + hostPort = net.JoinHostPort(hostPort, "443") } else { - hostPort = net.JoinHostPort(ch.Value, strconv.Itoa(port)) + hostPort = net.JoinHostPort(hostPort, strconv.Itoa(port)) } vc := MustClientFromContext(ctx) @@ -211,7 +246,7 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON "cannot negotiate ALPN acme-tls/1 protocol for tls-alpn-01 challenge")) } return storeError(ctx, db, ch, false, WrapError(ErrorConnectionType, err, - "error doing TLS dial for %s", hostPort)) + "error doing TLS dial for %s", ch.Value)) } defer conn.Close() @@ -307,7 +342,7 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK domain := strings.TrimPrefix(ch.Value, "*.") vc := MustClientFromContext(ctx) - txtRecords, err := vc.LookupTxt("_acme-challenge." + domain) + txtRecords, err := vc.LookupTxt(dns01ChallengeHost(domain)) if err != nil { return storeError(ctx, db, ch, false, WrapError(ErrorDNSType, err, "error looking up TXT records for domain %s", domain)) @@ -372,12 +407,26 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose attObj, err := base64.RawURLEncoding.DecodeString(p.AttObj) if err != nil { - return WrapErrorISE(err, "error base64 decoding attObj") + return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "failed base64 decoding attObj %q", p.AttObj)) + } + + if len(attObj) == 0 || bytes.Equal(attObj, []byte("{}")) { + return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "attObj must not be empty")) + } + + cborDecoderOptions := cbor.DecOptions{} + cborDecoder, err := cborDecoderOptions.DecMode() + if err != nil { + return WrapErrorISE(err, "failed creating CBOR decoder") + } + + if err := cborDecoder.Wellformed(attObj); err != nil { + return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "attObj is not well formed CBOR: %v", err)) } att := attestationObject{} - if err := cbor.Unmarshal(attObj, &att); err != nil { - return WrapErrorISE(err, "error unmarshalling CBOR") + if err := cborDecoder.Unmarshal(attObj, &att); err != nil { + return WrapErrorISE(err, "failed unmarshalling CBOR") } format := att.Format @@ -726,7 +775,7 @@ var ( oidTCGKpAIKCertificate = asn1.ObjectIdentifier{2, 23, 133, 8, 3} ) -// validateAKCertifiate validates the X.509 AK certificate to be +// validateAKCertificate validates the X.509 AK certificate to be // in accordance with the required properties. The requirements come from: // https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements. // @@ -735,7 +784,7 @@ var ( // - The Subject Alternative Name extension MUST be set as defined // in [TPMv2-EK-Profile] section 3.2.9. // - The Extended Key Usage extension MUST contain the OID 2.23.133.8.3 -// ("joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)"). +// ("joint-iso-itu-t(2) international-organizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)"). // - The Basic Constraints extension MUST have the CA component set to false. // - An Authority Information Access (AIA) extension with entry id-ad-ocsp // and a CRL Distribution Point extension [RFC5280] are both OPTIONAL as @@ -1058,14 +1107,10 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, // should be the ARPA address https://datatracker.ietf.org/doc/html/rfc8738#section-6. // It also references TLS Extensions [RFC6066]. func serverName(ch *Challenge) string { - var serverName string - ip := net.ParseIP(ch.Value) - if ip != nil { - serverName = reverseAddr(ip) - } else { - serverName = ch.Value + if ip := net.ParseIP(ch.Value); ip != nil { + return reverseAddr(ip) } - return serverName + return ch.Value } // reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 5cede1c5..40f86cb6 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -646,7 +646,7 @@ func TestChallenge_Validate(t *testing.T) { assert.Equal(t, "zap.internal", updch.Value) assert.Equal(t, StatusPending, updch.Status) - err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: force", ch.Value) + err := NewError(ErrorConnectionType, "error doing TLS dial for %v: force", ch.Value) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) @@ -1723,7 +1723,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equal(t, ChallengeType("tls-alpn-01"), updch.Type) assert.Equal(t, "zap.internal", updch.Value) - err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: force", ch.Value) + err := NewError(ErrorConnectionType, "error doing TLS dial for %v: force", ch.Value) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) @@ -1754,7 +1754,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equal(t, ChallengeType("tls-alpn-01"), updch.Type) assert.Equal(t, "zap.internal", updch.Value) - err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: force", ch.Value) + err := NewError(ErrorConnectionType, "error doing TLS dial for %v: force", ch.Value) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) @@ -1786,7 +1786,7 @@ func TestTLSALPN01Validate(t *testing.T) { assert.Equal(t, ChallengeType("tls-alpn-01"), updch.Type) assert.Equal(t, "zap.internal", updch.Value) - err := NewError(ErrorConnectionType, "error doing TLS dial for %v:443: context deadline exceeded", ch.Value) + err := NewError(ErrorConnectionType, "error doing TLS dial for %v: context deadline exceeded", ch.Value) assert.EqualError(t, updch.Error.Err, err.Err.Error()) assert.Equal(t, err.Type, updch.Error.Type) @@ -2767,14 +2767,34 @@ func Test_serverName(t *testing.T) { func Test_http01ChallengeHost(t *testing.T) { tests := []struct { - name string - value string - want string + name string + strictFQDN bool + value string + want string }{ { - name: "dns", - value: "www.example.com", - want: "www.example.com", + name: "dns", + strictFQDN: false, + value: "www.example.com", + want: "www.example.com", + }, + { + name: "dns strict", + strictFQDN: true, + value: "www.example.com", + want: "www.example.com.", + }, + { + name: "rooted dns", + strictFQDN: false, + value: "www.example.com.", + want: "www.example.com.", + }, + { + name: "rooted dns strict", + strictFQDN: true, + value: "www.example.com.", + want: "www.example.com.", }, { name: "ipv4", @@ -2789,6 +2809,11 @@ func Test_http01ChallengeHost(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tmp := StrictFQDN + t.Cleanup(func() { + StrictFQDN = tmp + }) + StrictFQDN = tt.strictFQDN if got := http01ChallengeHost(tt.value); got != tt.want { t.Errorf("http01ChallengeHost() = %v, want %v", got, tt.want) } @@ -3263,10 +3288,50 @@ func Test_deviceAttest01Validate(t *testing.T) { AttObj: "?!", }) require.NoError(t, err) - errorCBORPayload, err := json.Marshal(struct { + emptyPayload, err := json.Marshal(struct { AttObj string `json:"attObj"` }{ - AttObj: "AAAA", + AttObj: base64.RawURLEncoding.EncodeToString([]byte("")), + }) + require.NoError(t, err) + emptyObjectPayload, err := json.Marshal(struct { + AttObj string `json:"attObj"` + }{ + AttObj: base64.RawURLEncoding.EncodeToString([]byte("{}")), + }) + require.NoError(t, err) + attObj, err := cbor.Marshal(struct { + Format string `json:"fmt"` + AttStatement map[string]interface{} `json:"attStmt,omitempty"` + }{ + Format: "step", + AttStatement: map[string]interface{}{ + "alg": -7, + "sig": "", + }, + }) + require.NoError(t, err) + errorNonWellformedCBORPayload, err := json.Marshal(struct { + AttObj string `json:"attObj"` + }{ + AttObj: base64.RawURLEncoding.EncodeToString(attObj[:len(attObj)-1]), // cut the CBOR encoded data off + }) + require.NoError(t, err) + unsupportedFormatAttObj, err := cbor.Marshal(struct { + Format string `json:"fmt"` + AttStatement map[string]interface{} `json:"attStmt,omitempty"` + }{ + Format: "unsupported-format", + AttStatement: map[string]interface{}{ + "alg": -7, + "sig": "", + }, + }) + require.NoError(t, err) + errorUnsupportedFormat, err := json.Marshal(struct { + AttObj string `json:"attObj"` + }{ + AttObj: base64.RawURLEncoding.EncodeToString(unsupportedFormatAttObj), }) require.NoError(t, err) type args struct { @@ -3403,7 +3468,7 @@ func Test_deviceAttest01Validate(t *testing.T) { wantErr: nil, } }, - "fail/base64-decode": func(t *testing.T) test { + "ok/base64-decode": func(t *testing.T) test { return test{ args: args{ ch: &Challenge{ @@ -3419,13 +3484,29 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, "azID", id) return &Authorization{ID: "azID"}, nil }, + MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { + assert.Equal(t, "chID", updch.ID) + assert.Equal(t, "token", updch.Token) + assert.Equal(t, StatusInvalid, updch.Status) + assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) + assert.Equal(t, "12345678", updch.Value) + + err := NewDetailedError(ErrorBadAttestationStatementType, "failed base64 decoding attObj %q", "?!") + + assert.EqualError(t, updch.Error.Err, err.Err.Error()) + assert.Equal(t, err.Type, updch.Error.Type) + assert.Equal(t, err.Detail, updch.Error.Detail) + assert.Equal(t, err.Status, updch.Error.Status) + assert.Equal(t, err.Subproblems, updch.Error.Subproblems) + + return nil + }, }, payload: errorBase64Payload, }, - wantErr: NewErrorISE("error base64 decoding attObj: illegal base64 data at input byte 0"), } }, - "fail/cbor.Unmarshal": func(t *testing.T) test { + "ok/empty-attobj": func(t *testing.T) test { return test{ args: args{ ch: &Challenge{ @@ -3441,10 +3522,142 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, "azID", id) return &Authorization{ID: "azID"}, nil }, + MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { + assert.Equal(t, "chID", updch.ID) + assert.Equal(t, "token", updch.Token) + assert.Equal(t, StatusInvalid, updch.Status) + assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) + assert.Equal(t, "12345678", updch.Value) + + err := NewDetailedError(ErrorBadAttestationStatementType, "attObj must not be empty") + + assert.EqualError(t, updch.Error.Err, err.Err.Error()) + assert.Equal(t, err.Type, updch.Error.Type) + assert.Equal(t, err.Detail, updch.Error.Detail) + assert.Equal(t, err.Status, updch.Error.Status) + assert.Equal(t, err.Subproblems, updch.Error.Subproblems) + + return nil + }, }, - payload: errorCBORPayload, + payload: emptyPayload, + }, + } + }, + "ok/empty-json-attobj": func(t *testing.T) test { + return test{ + args: args{ + ch: &Challenge{ + ID: "chID", + AuthorizationID: "azID", + Token: "token", + Type: "device-attest-01", + Status: StatusPending, + Value: "12345678", + }, + db: &MockDB{ + MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) { + assert.Equal(t, "azID", id) + return &Authorization{ID: "azID"}, nil + }, + MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { + assert.Equal(t, "chID", updch.ID) + assert.Equal(t, "token", updch.Token) + assert.Equal(t, StatusInvalid, updch.Status) + assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) + assert.Equal(t, "12345678", updch.Value) + + err := NewDetailedError(ErrorBadAttestationStatementType, "attObj must not be empty") + + assert.EqualError(t, updch.Error.Err, err.Err.Error()) + assert.Equal(t, err.Type, updch.Error.Type) + assert.Equal(t, err.Detail, updch.Error.Detail) + assert.Equal(t, err.Status, updch.Error.Status) + assert.Equal(t, err.Subproblems, updch.Error.Subproblems) + + return nil + }, + }, + payload: emptyObjectPayload, + }, + } + }, + "ok/cborDecoder.Wellformed": func(t *testing.T) test { + return test{ + args: args{ + ch: &Challenge{ + ID: "chID", + AuthorizationID: "azID", + Token: "token", + Type: "device-attest-01", + Status: StatusPending, + Value: "12345678", + }, + db: &MockDB{ + MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) { + assert.Equal(t, "azID", id) + return &Authorization{ID: "azID"}, nil + }, + MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { + assert.Equal(t, "chID", updch.ID) + assert.Equal(t, "token", updch.Token) + assert.Equal(t, StatusInvalid, updch.Status) + assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) + assert.Equal(t, "12345678", updch.Value) + + err := NewDetailedError(ErrorBadAttestationStatementType, "attObj is not well formed CBOR: unexpected EOF") + + assert.EqualError(t, updch.Error.Err, err.Err.Error()) + assert.Equal(t, err.Type, updch.Error.Type) + assert.Equal(t, err.Detail, updch.Error.Detail) + assert.Equal(t, err.Status, updch.Error.Status) + assert.Equal(t, err.Subproblems, updch.Error.Subproblems) + + return nil + }, + }, + payload: errorNonWellformedCBORPayload, + }, + } + }, + "ok/unsupported-attestation-format": func(t *testing.T) test { + ctx := NewProvisionerContext(context.Background(), mustNonAttestationProvisioner(t)) + return test{ + args: args{ + ctx: ctx, + ch: &Challenge{ + ID: "chID", + AuthorizationID: "azID", + Token: "token", + Type: "device-attest-01", + Status: StatusPending, + Value: "12345678", + }, + db: &MockDB{ + MockGetAuthorization: func(ctx context.Context, id string) (*Authorization, error) { + assert.Equal(t, "azID", id) + return &Authorization{ID: "azID"}, nil + }, + MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error { + assert.Equal(t, "chID", updch.ID) + assert.Equal(t, "token", updch.Token) + assert.Equal(t, StatusInvalid, updch.Status) + assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) + assert.Equal(t, "12345678", updch.Value) + + err := NewDetailedError(ErrorBadAttestationStatementType, "unsupported attestation object format %q", "unsupported-format") + + assert.EqualError(t, updch.Error.Err, err.Err.Error()) + assert.Equal(t, err.Type, updch.Error.Type) + assert.Equal(t, err.Detail, updch.Error.Detail) + assert.Equal(t, err.Status, updch.Error.Status) + assert.Equal(t, err.Subproblems, updch.Error.Subproblems) + + return nil + }, + }, + payload: errorUnsupportedFormat, }, - wantErr: NewErrorISE("error unmarshalling CBOR: cbor:"), } }, "ok/prov.IsAttestationFormatEnabled": func(t *testing.T) test { @@ -4301,3 +4514,59 @@ func createSubjectAltNameExtension(dnsNames, emailAddresses x509util.MultiString Value: rawBytes, }, nil } + +func Test_tlsAlpn01ChallengeHost(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + strictFQDN bool + args args + want string + }{ + {"dns", false, args{"smallstep.com"}, "smallstep.com"}, + {"dns strict", true, args{"smallstep.com"}, "smallstep.com."}, + {"rooted dns", false, args{"smallstep.com."}, "smallstep.com."}, + {"rooted dns strict", true, args{"smallstep.com."}, "smallstep.com."}, + {"ipv4", true, args{"1.2.3.4"}, "1.2.3.4"}, + {"ipv6", true, args{"2607:f8b0:4023:1009::71"}, "2607:f8b0:4023:1009::71"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmp := StrictFQDN + t.Cleanup(func() { + StrictFQDN = tmp + }) + StrictFQDN = tt.strictFQDN + assert.Equal(t, tt.want, tlsAlpn01ChallengeHost(tt.args.name)) + }) + } +} + +func Test_dns01ChallengeHost(t *testing.T) { + type args struct { + domain string + } + tests := []struct { + name string + strictFQDN bool + args args + want string + }{ + {"dns", false, args{"smallstep.com"}, "_acme-challenge.smallstep.com"}, + {"dns strict", true, args{"smallstep.com"}, "_acme-challenge.smallstep.com."}, + {"rooted dns", false, args{"smallstep.com."}, "_acme-challenge.smallstep.com."}, + {"rooted dns strict", true, args{"smallstep.com."}, "_acme-challenge.smallstep.com."}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmp := StrictFQDN + t.Cleanup(func() { + StrictFQDN = tmp + }) + StrictFQDN = tt.strictFQDN + assert.Equal(t, tt.want, dns01ChallengeHost(tt.args.domain)) + }) + } +} diff --git a/acme/common.go b/acme/common.go index e86b23e9..a9a023cd 100644 --- a/acme/common.go +++ b/acme/common.go @@ -130,7 +130,7 @@ func (m *MockProvisioner) GetName() string { return m.Mret1.(string) } -// AuthorizeOrderIdentifiers mock +// AuthorizeOrderIdentifier mock func (m *MockProvisioner) AuthorizeOrderIdentifier(ctx context.Context, identifier provisioner.ACMEIdentifier) error { if m.MauthorizeOrderIdentifier != nil { return m.MauthorizeOrderIdentifier(ctx, identifier) diff --git a/acme/db.go b/acme/db.go index 4cbb3089..597eb3ad 100644 --- a/acme/db.go +++ b/acme/db.go @@ -2,6 +2,7 @@ package acme import ( "context" + "database/sql" "github.com/pkg/errors" ) @@ -15,7 +16,7 @@ var ErrNotFound = errors.New("not found") // IsErrNotFound returns true if the error is a "not found" error. Returns false // otherwise. func IsErrNotFound(err error) bool { - return errors.Is(err, ErrNotFound) + return errors.Is(err, ErrNotFound) || errors.Is(err, sql.ErrNoRows) } // DB is the DB interface expected by the step-ca ACME API. diff --git a/acme/db/nosql/account.go b/acme/db/nosql/account.go index d590ccb3..9b03db81 100644 --- a/acme/db/nosql/account.go +++ b/acme/db/nosql/account.go @@ -18,6 +18,7 @@ type dbAccount struct { Contact []string `json:"contact,omitempty"` Status acme.Status `json:"status"` LocationPrefix string `json:"locationPrefix"` + ProvisionerID string `json:"provisionerID,omitempty"` ProvisionerName string `json:"provisionerName"` CreatedAt time.Time `json:"createdAt"` DeactivatedAt time.Time `json:"deactivatedAt"` @@ -69,6 +70,7 @@ func (db *DB) GetAccount(ctx context.Context, id string) (*acme.Account, error) Key: dbacc.Key, ID: dbacc.ID, LocationPrefix: dbacc.LocationPrefix, + ProvisionerID: dbacc.ProvisionerID, ProvisionerName: dbacc.ProvisionerName, }, nil } @@ -97,6 +99,7 @@ func (db *DB) CreateAccount(ctx context.Context, acc *acme.Account) error { Status: acc.Status, CreatedAt: clock.Now(), LocationPrefix: acc.LocationPrefix, + ProvisionerID: acc.ProvisionerID, ProvisionerName: acc.ProvisionerName, } diff --git a/acme/db/nosql/account_test.go b/acme/db/nosql/account_test.go index 085ce2eb..4eacd0e7 100644 --- a/acme/db/nosql/account_test.go +++ b/acme/db/nosql/account_test.go @@ -68,12 +68,14 @@ func TestDB_getDBAccount(t *testing.T) { jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) assert.FatalError(t, err) dbacc := &dbAccount{ - ID: accID, - Status: acme.StatusDeactivated, - CreatedAt: now, - DeactivatedAt: now, - Contact: []string{"foo", "bar"}, - Key: jwk, + ID: accID, + Status: acme.StatusDeactivated, + CreatedAt: now, + DeactivatedAt: now, + Contact: []string{"foo", "bar"}, + Key: jwk, + ProvisionerID: "73d2c0f1-9753-448b-9b48-bf00fe434681", + ProvisionerName: "acme", } b, err := json.Marshal(dbacc) assert.FatalError(t, err) diff --git a/acme/db_test.go b/acme/db_test.go new file mode 100644 index 00000000..7d6b8722 --- /dev/null +++ b/acme/db_test.go @@ -0,0 +1,32 @@ +package acme + +import ( + "database/sql" + "errors" + "fmt" + "testing" +) + +func TestIsErrNotFound(t *testing.T) { + type args struct { + err error + } + tests := []struct { + name string + args args + want bool + }{ + {"true ErrNotFound", args{ErrNotFound}, true}, + {"true sql.ErrNoRows", args{sql.ErrNoRows}, true}, + {"true wrapped ErrNotFound", args{fmt.Errorf("something failed: %w", ErrNotFound)}, true}, + {"true wrapped sql.ErrNoRows", args{fmt.Errorf("something failed: %w", sql.ErrNoRows)}, true}, + {"false other", args{errors.New("not found")}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsErrNotFound(tt.args.err); got != tt.want { + t.Errorf("IsErrNotFound() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/acme/errors.go b/acme/errors.go index 658ec6e0..586cfb9b 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -424,7 +424,7 @@ func (e *Error) ToLog() (interface{}, error) { } // Render implements render.RenderableError for Error. -func (e *Error) Render(w http.ResponseWriter) { +func (e *Error) Render(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/problem+json") - render.JSONStatus(w, e, e.StatusCode()) + render.JSONStatus(w, r, e, e.StatusCode()) } diff --git a/acme/linker.go b/acme/linker.go index d142bf10..18997c5c 100644 --- a/acme/linker.go +++ b/acme/linker.go @@ -186,19 +186,19 @@ func (l *linker) Middleware(next http.Handler) http.Handler { nameEscaped := chi.URLParam(r, "provisionerID") name, err := url.PathUnescape(nameEscaped) if err != nil { - render.Error(w, WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped)) + render.Error(w, r, WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped)) return } p, err := authority.MustFromContext(ctx).LoadProvisionerByName(name) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } acmeProv, ok := p.(*provisioner.ACME) if !ok { - render.Error(w, NewError(ErrorAccountDoesNotExistType, "provisioner must be of type ACME")) + render.Error(w, r, NewError(ErrorAccountDoesNotExistType, "provisioner must be of type ACME")) return } diff --git a/acme/order.go b/acme/order.go index 5a86c2c8..13c16b27 100644 --- a/acme/order.go +++ b/acme/order.go @@ -127,7 +127,7 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error { return nil } -// getKeyFingerprint returns a fingerprint from the list of authorizations. This +// getAuthorizationFingerprint returns a fingerprint from the list of authorizations. This // fingerprint is used on the device-attest-01 flow to verify the attestation // certificate public key with the CSR public key. // diff --git a/api/api.go b/api/api.go index 6916983b..0b139a71 100644 --- a/api/api.go +++ b/api/api.go @@ -353,15 +353,15 @@ func Route(r Router) { // Version is an HTTP handler that returns the version of the server. func Version(w http.ResponseWriter, r *http.Request) { v := mustAuthority(r.Context()).Version() - render.JSON(w, VersionResponse{ + render.JSON(w, r, VersionResponse{ Version: v.Version, RequireClientAuthentication: v.RequireClientAuthentication, }) } // Health is an HTTP handler that returns the status of the server. -func Health(w http.ResponseWriter, _ *http.Request) { - render.JSON(w, HealthResponse{Status: "ok"}) +func Health(w http.ResponseWriter, r *http.Request) { + render.JSON(w, r, HealthResponse{Status: "ok"}) } // Root is an HTTP handler that using the SHA256 from the URL, returns the root @@ -372,11 +372,11 @@ func Root(w http.ResponseWriter, r *http.Request) { // Load root certificate with the cert, err := mustAuthority(r.Context()).Root(sum) if err != nil { - render.Error(w, errs.Wrapf(http.StatusNotFound, err, "%s was not found", r.RequestURI)) + render.Error(w, r, errs.Wrapf(http.StatusNotFound, err, "%s was not found", r.RequestURI)) return } - render.JSON(w, &RootResponse{RootPEM: Certificate{cert}}) + render.JSON(w, r, &RootResponse{RootPEM: Certificate{cert}}) } func certChainToPEM(certChain []*x509.Certificate) []Certificate { @@ -391,17 +391,17 @@ func certChainToPEM(certChain []*x509.Certificate) []Certificate { func Provisioners(w http.ResponseWriter, r *http.Request) { cursor, limit, err := ParseCursor(r) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } - render.JSON(w, &ProvisionersResponse{ + render.JSON(w, r, &ProvisionersResponse{ Provisioners: p, NextCursor: next, }) @@ -412,18 +412,18 @@ func ProvisionerKey(w http.ResponseWriter, r *http.Request) { kid := chi.URLParam(r, "kid") key, err := mustAuthority(r.Context()).GetEncryptedKey(kid) if err != nil { - render.Error(w, errs.NotFoundErr(err)) + render.Error(w, r, errs.NotFoundErr(err)) return } - render.JSON(w, &ProvisionerKeyResponse{key}) + render.JSON(w, r, &ProvisionerKeyResponse{key}) } // Roots returns all the root certificates for the CA. func Roots(w http.ResponseWriter, r *http.Request) { roots, err := mustAuthority(r.Context()).GetRoots() if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error getting roots")) + render.Error(w, r, errs.ForbiddenErr(err, "error getting roots")) return } @@ -432,7 +432,7 @@ func Roots(w http.ResponseWriter, r *http.Request) { certs[i] = Certificate{roots[i]} } - render.JSONStatus(w, &RootsResponse{ + render.JSONStatus(w, r, &RootsResponse{ Certificates: certs, }, http.StatusCreated) } @@ -441,7 +441,7 @@ func Roots(w http.ResponseWriter, r *http.Request) { func RootsPEM(w http.ResponseWriter, r *http.Request) { roots, err := mustAuthority(r.Context()).GetRoots() if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } @@ -454,7 +454,7 @@ func RootsPEM(w http.ResponseWriter, r *http.Request) { }) if _, err := w.Write(block); err != nil { - log.Error(w, err) + log.Error(w, r, err) return } } @@ -464,7 +464,7 @@ func RootsPEM(w http.ResponseWriter, r *http.Request) { func Federation(w http.ResponseWriter, r *http.Request) { federated, err := mustAuthority(r.Context()).GetFederation() if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error getting federated roots")) + render.Error(w, r, errs.ForbiddenErr(err, "error getting federated roots")) return } @@ -473,7 +473,7 @@ func Federation(w http.ResponseWriter, r *http.Request) { certs[i] = Certificate{federated[i]} } - render.JSONStatus(w, &FederationResponse{ + render.JSONStatus(w, r, &FederationResponse{ Certificates: certs, }, http.StatusCreated) } diff --git a/api/crl.go b/api/crl.go index c10d08ca..92e14815 100644 --- a/api/crl.go +++ b/api/crl.go @@ -13,12 +13,12 @@ import ( func CRL(w http.ResponseWriter, r *http.Request) { crlInfo, err := mustAuthority(r.Context()).GetCertificateRevocationList() if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if crlInfo == nil { - render.Error(w, errs.New(http.StatusNotFound, "no CRL available")) + render.Error(w, r, errs.New(http.StatusNotFound, "no CRL available")) return } diff --git a/api/log/log.go b/api/log/log.go index 687d61c6..6cc61a77 100644 --- a/api/log/log.go +++ b/api/log/log.go @@ -2,6 +2,7 @@ package log import ( + "context" "fmt" "net/http" "os" @@ -9,6 +10,29 @@ import ( "github.com/pkg/errors" ) +type errorLoggerKey struct{} + +// ErrorLogger is the function type used to log errors. +type ErrorLogger func(http.ResponseWriter, *http.Request, error) + +func (fn ErrorLogger) call(w http.ResponseWriter, r *http.Request, err error) { + if fn == nil { + return + } + fn(w, r, err) +} + +// WithErrorLogger returns a new context with the given error logger. +func WithErrorLogger(ctx context.Context, fn ErrorLogger) context.Context { + return context.WithValue(ctx, errorLoggerKey{}, fn) +} + +// ErrorLoggerFromContext returns an error logger from the context. +func ErrorLoggerFromContext(ctx context.Context) (fn ErrorLogger) { + fn, _ = ctx.Value(errorLoggerKey{}).(ErrorLogger) + return +} + // StackTracedError is the set of errors implementing the StackTrace function. // // Errors implementing this interface have their stack traces logged when passed @@ -27,8 +51,10 @@ type fieldCarrier interface { // Error adds to the response writer the given error if it implements // logging.ResponseLogger. If it does not implement it, then writes the error // using the log package. -func Error(rw http.ResponseWriter, err error) { - fc, ok := rw.(fieldCarrier) +func Error(w http.ResponseWriter, r *http.Request, err error) { + ErrorLoggerFromContext(r.Context()).call(w, r, err) + + fc, ok := w.(fieldCarrier) if !ok { return } @@ -51,7 +77,7 @@ func Error(rw http.ResponseWriter, err error) { // EnabledResponse log the response object if it implements the EnableLogger // interface. -func EnabledResponse(rw http.ResponseWriter, v any) { +func EnabledResponse(rw http.ResponseWriter, r *http.Request, v any) { type enableLogger interface { ToLog() (any, error) } @@ -59,7 +85,7 @@ func EnabledResponse(rw http.ResponseWriter, v any) { if el, ok := v.(enableLogger); ok { out, err := el.ToLog() if err != nil { - Error(rw, err) + Error(rw, r, err) return } diff --git a/api/log/log_test.go b/api/log/log_test.go index 7c08b771..e1da274f 100644 --- a/api/log/log_test.go +++ b/api/log/log_test.go @@ -1,6 +1,9 @@ package log import ( + "bytes" + "encoding/json" + "log/slog" "net/http" "net/http/httptest" "testing" @@ -27,21 +30,34 @@ func (stackTracedError) StackTrace() pkgerrors.StackTrace { } func TestError(t *testing.T) { + var buf bytes.Buffer + logger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{})) + req := httptest.NewRequest("GET", "/test", http.NoBody) + reqWithLogger := req.WithContext(WithErrorLogger(req.Context(), func(w http.ResponseWriter, r *http.Request, err error) { + if err != nil { + logger.ErrorContext(r.Context(), "request failed", slog.Any("error", err)) + } + })) + tests := []struct { name string error rw http.ResponseWriter + r *http.Request isFieldCarrier bool + isSlogLogger bool stepDebug bool expectStackTrace bool }{ - {"noLogger", nil, nil, false, false, false}, - {"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false}, - {"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false}, - {"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false}, - {"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false}, - {"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true}, - {"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true}, + {"noLogger", nil, nil, req, false, false, false, false}, + {"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, false, false}, + {"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, false}, + {"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, false, false}, + {"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, false}, + {"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, true}, + {"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), req, true, false, true, true}, + {"slogWithNoError", nil, logging.NewResponseLogger(httptest.NewRecorder()), reqWithLogger, true, true, false, false}, + {"slogWithError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), reqWithLogger, true, true, false, false}, } for _, tt := range tests { @@ -52,27 +68,41 @@ func TestError(t *testing.T) { t.Setenv("STEPDEBUG", "0") } - Error(tt.rw, tt.error) + Error(tt.rw, tt.r, tt.error) // return early if test case doesn't use logger - if !tt.isFieldCarrier { + if !tt.isFieldCarrier && !tt.isSlogLogger { return } - fields := tt.rw.(logging.ResponseLogger).Fields() + if tt.isFieldCarrier { + fields := tt.rw.(logging.ResponseLogger).Fields() - // expect the error field to be (not) set and to be the same error that was fed to Error - if tt.error == nil { - assert.Nil(t, fields["error"]) - } else { - assert.Same(t, tt.error, fields["error"]) + // expect the error field to be (not) set and to be the same error that was fed to Error + if tt.error == nil { + assert.Nil(t, fields["error"]) + } else { + assert.Same(t, tt.error, fields["error"]) + } + + // check if stack-trace is set when expected + if _, hasStackTrace := fields["stack-trace"]; tt.expectStackTrace && !hasStackTrace { + t.Error(`ResponseLogger["stack-trace"] not set`) + } else if !tt.expectStackTrace && hasStackTrace { + t.Error(`ResponseLogger["stack-trace"] was set`) + } } - // check if stack-trace is set when expected - if _, hasStackTrace := fields["stack-trace"]; tt.expectStackTrace && !hasStackTrace { - t.Error(`ResponseLogger["stack-trace"] not set`) - } else if !tt.expectStackTrace && hasStackTrace { - t.Error(`ResponseLogger["stack-trace"] was set`) + if tt.isSlogLogger { + b := buf.Bytes() + if tt.error == nil { + assert.Empty(t, b) + } else if assert.NotEmpty(t, b) { + var m map[string]any + assert.NoError(t, json.Unmarshal(b, &m)) + assert.Equal(t, tt.error.Error(), m["error"]) + } + buf.Reset() } }) } diff --git a/api/models/scep.go b/api/models/scep.go index f4aa1502..7b3c0793 100644 --- a/api/models/scep.go +++ b/api/models/scep.go @@ -97,7 +97,7 @@ func (s *SCEP) AuthorizeSSHSign(context.Context, string) ([]provisioner.SignOpti return nil, errDummyImplementation } -// AuthorizeRevoke returns an unimplemented error. Provisioners should overwrite +// AuthorizeSSHRevoke returns an unimplemented error. Provisioners should overwrite // this method if they will support authorizing tokens for revoking SSH Certificates. func (s *SCEP) AuthorizeSSHRevoke(context.Context, string) error { return errDummyImplementation diff --git a/api/read/read.go b/api/read/read.go index 72530b8c..6f75c41a 100644 --- a/api/read/read.go +++ b/api/read/read.go @@ -51,7 +51,7 @@ func (e badProtoJSONError) Error() string { } // Render implements render.RenderableError for badProtoJSONError -func (e badProtoJSONError) Render(w http.ResponseWriter) { +func (e badProtoJSONError) Render(w http.ResponseWriter, r *http.Request) { v := struct { Type string `json:"type"` Detail string `json:"detail"` @@ -62,5 +62,5 @@ func (e badProtoJSONError) Render(w http.ResponseWriter) { // trim the proto prefix for the message Message: strings.TrimSpace(strings.TrimPrefix(e.Error(), "proto:")), } - render.JSONStatus(w, v, http.StatusBadRequest) + render.JSONStatus(w, r, v, http.StatusBadRequest) } diff --git a/api/read/read_test.go b/api/read/read_test.go index e46e7f61..e557a9a2 100644 --- a/api/read/read_test.go +++ b/api/read/read_test.go @@ -142,7 +142,8 @@ func Test_badProtoJSONError_Render(t *testing.T) { t.Run(tt.name, func(t *testing.T) { w := httptest.NewRecorder() - tt.e.Render(w) + r := httptest.NewRequest("POST", "/test", http.NoBody) + tt.e.Render(w, r) res := w.Result() defer res.Body.Close() diff --git a/api/rekey.go b/api/rekey.go index cda843a3..772de217 100644 --- a/api/rekey.go +++ b/api/rekey.go @@ -29,25 +29,25 @@ func (s *RekeyRequest) Validate() error { // Rekey is similar to renew except that the certificate will be renewed with new key from csr. func Rekey(w http.ResponseWriter, r *http.Request) { if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 { - render.Error(w, errs.BadRequest("missing client certificate")) + render.Error(w, r, errs.BadRequest("missing client certificate")) return } var body RekeyRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } a := mustAuthority(r.Context()) certChain, err := a.Rekey(r.TLS.PeerCertificates[0], body.CsrPEM.CertificateRequest.PublicKey) if err != nil { - render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey")) + render.Error(w, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey")) return } certChainPEM := certChainToPEM(certChain) @@ -57,7 +57,7 @@ func Rekey(w http.ResponseWriter, r *http.Request) { } LogCertificate(w, certChain[0]) - render.JSONStatus(w, &SignResponse{ + render.JSONStatus(w, r, &SignResponse{ ServerPEM: certChainPEM[0], CaPEM: caPEM, CertChainPEM: certChainPEM, diff --git a/api/render/render.go b/api/render/render.go index 7829ba25..1c66280c 100644 --- a/api/render/render.go +++ b/api/render/render.go @@ -13,8 +13,8 @@ import ( ) // JSON is shorthand for JSONStatus(w, v, http.StatusOK). -func JSON(w http.ResponseWriter, v interface{}) { - JSONStatus(w, v, http.StatusOK) +func JSON(w http.ResponseWriter, r *http.Request, v interface{}) { + JSONStatus(w, r, v, http.StatusOK) } // JSONStatus marshals v into w. It additionally sets the status code of @@ -22,7 +22,7 @@ func JSON(w http.ResponseWriter, v interface{}) { // // JSONStatus sets the Content-Type of w to application/json unless one is // specified. -func JSONStatus(w http.ResponseWriter, v interface{}, status int) { +func JSONStatus(w http.ResponseWriter, r *http.Request, v interface{}, status int) { setContentTypeUnlessPresent(w, "application/json") w.WriteHeader(status) @@ -43,7 +43,7 @@ func JSONStatus(w http.ResponseWriter, v interface{}, status int) { } } - log.EnabledResponse(w, v) + log.EnabledResponse(w, r, v) } // ProtoJSON is shorthand for ProtoJSONStatus(w, m, http.StatusOK). @@ -80,22 +80,22 @@ func setContentTypeUnlessPresent(w http.ResponseWriter, contentType string) { type RenderableError interface { error - Render(http.ResponseWriter) + Render(http.ResponseWriter, *http.Request) } // Error marshals the JSON representation of err to w. In case err implements // RenderableError its own Render method will be called instead. -func Error(w http.ResponseWriter, err error) { - log.Error(w, err) +func Error(rw http.ResponseWriter, r *http.Request, err error) { + log.Error(rw, r, err) - var r RenderableError - if errors.As(err, &r) { - r.Render(w) + var re RenderableError + if errors.As(err, &re) { + re.Render(rw, r) return } - JSONStatus(w, err, statusCodeFromError(err)) + JSONStatus(rw, r, err, statusCodeFromError(err)) } // StatusCodedError is the set of errors that implement the basic StatusCode diff --git a/api/render/render_test.go b/api/render/render_test.go index e88544c7..d7ee37fd 100644 --- a/api/render/render_test.go +++ b/api/render/render_test.go @@ -18,8 +18,8 @@ import ( func TestJSON(t *testing.T) { rec := httptest.NewRecorder() rw := logging.NewResponseLogger(rec) - - JSON(rw, map[string]interface{}{"foo": "bar"}) + r := httptest.NewRequest("POST", "/test", http.NoBody) + JSON(rw, r, map[string]interface{}{"foo": "bar"}) assert.Equal(t, http.StatusOK, rec.Result().StatusCode) assert.Equal(t, "application/json", rec.Header().Get("Content-Type")) @@ -64,7 +64,8 @@ func jsonPanicTest[T json.UnsupportedTypeError | json.UnsupportedValueError | js assert.ErrorAs(t, err, &e) }() - JSON(httptest.NewRecorder(), v) + r := httptest.NewRequest("POST", "/test", http.NoBody) + JSON(httptest.NewRecorder(), r, v) } type renderableError struct { @@ -76,10 +77,9 @@ func (err renderableError) Error() string { return err.Message } -func (err renderableError) Render(w http.ResponseWriter) { +func (err renderableError) Render(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "something/custom") - - JSONStatus(w, err, err.Code) + JSONStatus(w, r, err, err.Code) } type statusedError struct { @@ -116,8 +116,8 @@ func TestError(t *testing.T) { t.Run(strconv.Itoa(caseIndex), func(t *testing.T) { rec := httptest.NewRecorder() - - Error(rec, kase.err) + r := httptest.NewRequest("POST", "/test", http.NoBody) + Error(rec, r, kase.err) assert.Equal(t, kase.code, rec.Result().StatusCode) assert.Equal(t, kase.body, rec.Body.String()) diff --git a/api/renew.go b/api/renew.go index 1b9ed95f..7cd3707d 100644 --- a/api/renew.go +++ b/api/renew.go @@ -23,19 +23,20 @@ func Renew(w http.ResponseWriter, r *http.Request) { // Get the leaf certificate from the peer or the token. cert, token, err := getPeerCertificate(r) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } // The token can be used by RAs to renew a certificate. if token != "" { ctx = authority.NewTokenContext(ctx, token) + logOtt(w, token) } a := mustAuthority(ctx) certChain, err := a.RenewContext(ctx, cert, nil) if err != nil { - render.Error(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew")) + render.Error(w, r, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Renew")) return } certChainPEM := certChainToPEM(certChain) @@ -45,7 +46,7 @@ func Renew(w http.ResponseWriter, r *http.Request) { } LogCertificate(w, certChain[0]) - render.JSONStatus(w, &SignResponse{ + render.JSONStatus(w, r, &SignResponse{ ServerPEM: certChainPEM[0], CaPEM: caPEM, CertChainPEM: certChainPEM, diff --git a/api/revoke.go b/api/revoke.go index dc639d58..41969c08 100644 --- a/api/revoke.go +++ b/api/revoke.go @@ -57,12 +57,12 @@ func (r *RevokeRequest) Validate() (err error) { func Revoke(w http.ResponseWriter, r *http.Request) { var body RevokeRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -81,7 +81,7 @@ func Revoke(w http.ResponseWriter, r *http.Request) { if body.OTT != "" { logOtt(w, body.OTT) if _, err := a.Authorize(ctx, body.OTT); err != nil { - render.Error(w, errs.UnauthorizedErr(err)) + render.Error(w, r, errs.UnauthorizedErr(err)) return } opts.OTT = body.OTT @@ -90,12 +90,12 @@ func Revoke(w http.ResponseWriter, r *http.Request) { // the client certificate Serial Number must match the serial number // being revoked. if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 { - render.Error(w, errs.BadRequest("missing ott or client certificate")) + render.Error(w, r, errs.BadRequest("missing ott or client certificate")) return } opts.Crt = r.TLS.PeerCertificates[0] if opts.Crt.SerialNumber.String() != opts.Serial { - render.Error(w, errs.BadRequest("serial number in client certificate different than body")) + render.Error(w, r, errs.BadRequest("serial number in client certificate different than body")) return } // TODO: should probably be checking if the certificate was revoked here. @@ -106,12 +106,12 @@ func Revoke(w http.ResponseWriter, r *http.Request) { } if err := a.Revoke(ctx, opts); err != nil { - render.Error(w, errs.ForbiddenErr(err, "error revoking certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error revoking certificate")) return } logRevoke(w, opts) - render.JSON(w, &RevokeResponse{Status: "ok"}) + render.JSON(w, r, &RevokeResponse{Status: "ok"}) } func logRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) { diff --git a/api/sign.go b/api/sign.go index 26b3c396..bff41763 100644 --- a/api/sign.go +++ b/api/sign.go @@ -52,13 +52,13 @@ type SignResponse struct { func Sign(w http.ResponseWriter, r *http.Request) { var body SignRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } logOtt(w, body.OTT) if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -74,13 +74,13 @@ func Sign(w http.ResponseWriter, r *http.Request) { ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) signOpts, err := a.Authorize(ctx, body.OTT) if err != nil { - render.Error(w, errs.UnauthorizedErr(err)) + render.Error(w, r, errs.UnauthorizedErr(err)) return } certChain, err := a.SignWithContext(ctx, body.CsrPEM.CertificateRequest, opts, signOpts...) if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error signing certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error signing certificate")) return } certChainPEM := certChainToPEM(certChain) @@ -90,7 +90,7 @@ func Sign(w http.ResponseWriter, r *http.Request) { } LogCertificate(w, certChain[0]) - render.JSONStatus(w, &SignResponse{ + render.JSONStatus(w, r, &SignResponse{ ServerPEM: certChainPEM[0], CaPEM: caPEM, CertChainPEM: certChainPEM, diff --git a/api/ssh.go b/api/ssh.go index 08294c71..e1024f36 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -6,8 +6,11 @@ import ( "encoding/base64" "encoding/json" "net/http" + "net/url" + "strings" "time" + "github.com/google/uuid" "github.com/pkg/errors" "golang.org/x/crypto/ssh" @@ -253,19 +256,19 @@ type SSHBastionResponse struct { func SSHSign(w http.ResponseWriter, r *http.Request) { var body SSHSignRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } logOtt(w, body.OTT) if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } publicKey, err := ssh.ParsePublicKey(body.PublicKey) if err != nil { - render.Error(w, errs.BadRequestErr(err, "error parsing publicKey")) + render.Error(w, r, errs.BadRequestErr(err, "error parsing publicKey")) return } @@ -273,7 +276,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) { if body.AddUserPublicKey != nil { addUserPublicKey, err = ssh.ParsePublicKey(body.AddUserPublicKey) if err != nil { - render.Error(w, errs.BadRequestErr(err, "error parsing addUserPublicKey")) + render.Error(w, r, errs.BadRequestErr(err, "error parsing addUserPublicKey")) return } } @@ -289,17 +292,18 @@ func SSHSign(w http.ResponseWriter, r *http.Request) { ctx := provisioner.NewContextWithMethod(r.Context(), provisioner.SSHSignMethod) ctx = provisioner.NewContextWithToken(ctx, body.OTT) + ctx = provisioner.NewContextWithCertType(ctx, opts.CertType) a := mustAuthority(ctx) signOpts, err := a.Authorize(ctx, body.OTT) if err != nil { - render.Error(w, errs.UnauthorizedErr(err)) + render.Error(w, r, errs.UnauthorizedErr(err)) return } cert, err := a.SignSSH(ctx, publicKey, opts, signOpts...) if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error signing ssh certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error signing ssh certificate")) return } @@ -307,7 +311,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) { if addUserPublicKey != nil && authority.IsValidForAddUser(cert) == nil { addUserCert, err := a.SignSSHAddUser(ctx, addUserPublicKey, cert) if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error signing ssh certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error signing ssh certificate")) return } addUserCertificate = &SSHCertificate{addUserCert} @@ -320,26 +324,27 @@ func SSHSign(w http.ResponseWriter, r *http.Request) { ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignIdentityMethod) signOpts, err := a.Authorize(ctx, body.OTT) if err != nil { - render.Error(w, errs.UnauthorizedErr(err)) + render.Error(w, r, errs.UnauthorizedErr(err)) return } // Enforce the same duration as ssh certificate. signOpts = append(signOpts, &identityModifier{ + Identity: getIdentityURI(cr), NotBefore: time.Unix(int64(cert.ValidAfter), 0), NotAfter: time.Unix(int64(cert.ValidBefore), 0), }) certChain, err := a.SignWithContext(ctx, cr, provisioner.SignOptions{}, signOpts...) if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error signing identity certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error signing identity certificate")) return } identityCertificate = certChainToPEM(certChain) } LogSSHCertificate(w, cert) - render.JSONStatus(w, &SSHSignResponse{ + render.JSONStatus(w, r, &SSHSignResponse{ Certificate: SSHCertificate{cert}, AddUserCertificate: addUserCertificate, IdentityCertificate: identityCertificate, @@ -352,12 +357,12 @@ func SSHRoots(w http.ResponseWriter, r *http.Request) { ctx := r.Context() keys, err := mustAuthority(ctx).GetSSHRoots(ctx) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 { - render.Error(w, errs.NotFound("no keys found")) + render.Error(w, r, errs.NotFound("no keys found")) return } @@ -369,7 +374,7 @@ func SSHRoots(w http.ResponseWriter, r *http.Request) { resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k}) } - render.JSON(w, resp) + render.JSON(w, r, resp) } // SSHFederation is an HTTP handler that returns the federated SSH public keys @@ -378,12 +383,12 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) { ctx := r.Context() keys, err := mustAuthority(ctx).GetSSHFederation(ctx) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } if len(keys.HostKeys) == 0 && len(keys.UserKeys) == 0 { - render.Error(w, errs.NotFound("no keys found")) + render.Error(w, r, errs.NotFound("no keys found")) return } @@ -395,7 +400,7 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) { resp.UserKeys = append(resp.UserKeys, SSHPublicKey{PublicKey: k}) } - render.JSON(w, resp) + render.JSON(w, r, resp) } // SSHConfig is an HTTP handler that returns rendered templates for ssh clients @@ -403,18 +408,18 @@ func SSHFederation(w http.ResponseWriter, r *http.Request) { func SSHConfig(w http.ResponseWriter, r *http.Request) { var body SSHConfigRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } ctx := r.Context() ts, err := mustAuthority(ctx).GetSSHConfig(ctx, body.Type, body.Data) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } @@ -425,32 +430,32 @@ func SSHConfig(w http.ResponseWriter, r *http.Request) { case provisioner.SSHHostCert: cfg.HostTemplates = ts default: - render.Error(w, errs.InternalServer("it should hot get here")) + render.Error(w, r, errs.InternalServer("it should hot get here")) return } - render.JSON(w, cfg) + render.JSON(w, r, cfg) } // SSHCheckHost is the HTTP handler that returns if a hosts certificate exists or not. func SSHCheckHost(w http.ResponseWriter, r *http.Request) { var body SSHCheckPrincipalRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } ctx := r.Context() exists, err := mustAuthority(ctx).CheckSSHHost(ctx, body.Principal, body.Token) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } - render.JSON(w, &SSHCheckPrincipalResponse{ + render.JSON(w, r, &SSHCheckPrincipalResponse{ Exists: exists, }) } @@ -465,10 +470,10 @@ func SSHGetHosts(w http.ResponseWriter, r *http.Request) { ctx := r.Context() hosts, err := mustAuthority(ctx).GetSSHHosts(ctx, cert) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } - render.JSON(w, &SSHGetHostsResponse{ + render.JSON(w, r, &SSHGetHostsResponse{ Hosts: hosts, }) } @@ -477,35 +482,63 @@ func SSHGetHosts(w http.ResponseWriter, r *http.Request) { func SSHBastion(w http.ResponseWriter, r *http.Request) { var body SSHBastionRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } ctx := r.Context() bastion, err := mustAuthority(ctx).GetSSHBastion(ctx, body.User, body.Hostname) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } - render.JSON(w, &SSHBastionResponse{ + render.JSON(w, r, &SSHBastionResponse{ Hostname: body.Hostname, Bastion: bastion, }) } -// identityModifier is a custom modifier used to force a fixed duration. +// identityModifier is a custom modifier used to force a fixed duration, and set +// the identity URI. type identityModifier struct { + Identity *url.URL NotBefore time.Time NotAfter time.Time } +// Enforce implements the enforcer interface and sets the validity bounds and +// the identity uri to the certificate. func (m *identityModifier) Enforce(cert *x509.Certificate) error { cert.NotBefore = m.NotBefore cert.NotAfter = m.NotAfter + if m.Identity != nil { + var identityURL = m.Identity.String() + for _, u := range cert.URIs { + if u.String() == identityURL { + return nil + } + } + cert.URIs = append(cert.URIs, m.Identity) + } + + return nil +} + +// getIdentityURI returns the first valid UUID URN from the given CSR. +func getIdentityURI(cr *x509.CertificateRequest) *url.URL { + for _, u := range cr.URIs { + s := u.String() + // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if len(s) == 9+36 && strings.EqualFold(s[:9], "urn:uuid:") { + if _, err := uuid.Parse(s); err == nil { + return u + } + } + } return nil } diff --git a/api/sshRekey.go b/api/sshRekey.go index 80fc6d87..0db4d4da 100644 --- a/api/sshRekey.go +++ b/api/sshRekey.go @@ -42,19 +42,19 @@ type SSHRekeyResponse struct { func SSHRekey(w http.ResponseWriter, r *http.Request) { var body SSHRekeyRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } logOtt(w, body.OTT) if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } publicKey, err := ssh.ParsePublicKey(body.PublicKey) if err != nil { - render.Error(w, errs.BadRequestErr(err, "error parsing publicKey")) + render.Error(w, r, errs.BadRequestErr(err, "error parsing publicKey")) return } @@ -64,18 +64,18 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) { a := mustAuthority(ctx) signOpts, err := a.Authorize(ctx, body.OTT) if err != nil { - render.Error(w, errs.UnauthorizedErr(err)) + render.Error(w, r, errs.UnauthorizedErr(err)) return } oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } newCert, err := a.RekeySSH(ctx, oldCert, publicKey, signOpts...) if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error rekeying ssh certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error rekeying ssh certificate")) return } @@ -85,12 +85,12 @@ func SSHRekey(w http.ResponseWriter, r *http.Request) { identity, err := renewIdentityCertificate(r, notBefore, notAfter) if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error renewing identity certificate")) return } LogSSHCertificate(w, newCert) - render.JSONStatus(w, &SSHRekeyResponse{ + render.JSONStatus(w, r, &SSHRekeyResponse{ Certificate: SSHCertificate{newCert}, IdentityCertificate: identity, }, http.StatusCreated) diff --git a/api/sshRenew.go b/api/sshRenew.go index cd6d9bde..dea7cea7 100644 --- a/api/sshRenew.go +++ b/api/sshRenew.go @@ -40,13 +40,13 @@ type SSHRenewResponse struct { func SSHRenew(w http.ResponseWriter, r *http.Request) { var body SSHRenewRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } logOtt(w, body.OTT) if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -56,18 +56,18 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) { a := mustAuthority(ctx) _, err := a.Authorize(ctx, body.OTT) if err != nil { - render.Error(w, errs.UnauthorizedErr(err)) + render.Error(w, r, errs.UnauthorizedErr(err)) return } oldCert, _, err := provisioner.ExtractSSHPOPCert(body.OTT) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } newCert, err := a.RenewSSH(ctx, oldCert) if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error renewing ssh certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error renewing ssh certificate")) return } @@ -77,12 +77,12 @@ func SSHRenew(w http.ResponseWriter, r *http.Request) { identity, err := renewIdentityCertificate(r, notBefore, notAfter) if err != nil { - render.Error(w, errs.ForbiddenErr(err, "error renewing identity certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error renewing identity certificate")) return } LogSSHCertificate(w, newCert) - render.JSONStatus(w, &SSHSignResponse{ + render.JSONStatus(w, r, &SSHSignResponse{ Certificate: SSHCertificate{newCert}, IdentityCertificate: identity, }, http.StatusCreated) diff --git a/api/sshRevoke.go b/api/sshRevoke.go index d377def9..2fe49199 100644 --- a/api/sshRevoke.go +++ b/api/sshRevoke.go @@ -51,12 +51,12 @@ func (r *SSHRevokeRequest) Validate() (err error) { func SSHRevoke(w http.ResponseWriter, r *http.Request) { var body SSHRevokeRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, errs.BadRequestErr(err, "error reading request body")) + render.Error(w, r, errs.BadRequestErr(err, "error reading request body")) return } if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -75,18 +75,18 @@ func SSHRevoke(w http.ResponseWriter, r *http.Request) { logOtt(w, body.OTT) if _, err := a.Authorize(ctx, body.OTT); err != nil { - render.Error(w, errs.UnauthorizedErr(err)) + render.Error(w, r, errs.UnauthorizedErr(err)) return } opts.OTT = body.OTT if err := a.Revoke(ctx, opts); err != nil { - render.Error(w, errs.ForbiddenErr(err, "error revoking ssh certificate")) + render.Error(w, r, errs.ForbiddenErr(err, "error revoking ssh certificate")) return } logSSHRevoke(w, opts) - render.JSON(w, &SSHRevokeResponse{Status: "ok"}) + render.JSON(w, r, &SSHRevokeResponse{Status: "ok"}) } func logSSHRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) { diff --git a/api/ssh_test.go b/api/ssh_test.go index 2b90dc12..7d917fa7 100644 --- a/api/ssh_test.go +++ b/api/ssh_test.go @@ -13,18 +13,20 @@ import ( "io" "net/http" "net/http/httptest" + "net/url" "reflect" "strings" "testing" "time" - "golang.org/x/crypto/ssh" - - "github.com/smallstep/assert" + "github.com/google/uuid" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/templates" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/ssh" ) var ( @@ -123,9 +125,9 @@ func getSignedHostCertificate() (*ssh.Certificate, error) { func TestSSHCertificate_MarshalJSON(t *testing.T) { user, err := getSignedUserCertificate() - assert.FatalError(t, err) + require.NoError(t, err) host, err := getSignedHostCertificate() - assert.FatalError(t, err) + require.NoError(t, err) userB64 := base64.StdEncoding.EncodeToString(user.Marshal()) hostB64 := base64.StdEncoding.EncodeToString(host.Marshal()) @@ -161,9 +163,9 @@ func TestSSHCertificate_MarshalJSON(t *testing.T) { func TestSSHCertificate_UnmarshalJSON(t *testing.T) { user, err := getSignedUserCertificate() - assert.FatalError(t, err) + require.NoError(t, err) host, err := getSignedHostCertificate() - assert.FatalError(t, err) + require.NoError(t, err) userB64 := base64.StdEncoding.EncodeToString(user.Marshal()) hostB64 := base64.StdEncoding.EncodeToString(host.Marshal()) keyB64 := base64.StdEncoding.EncodeToString(user.Key.Marshal()) @@ -253,9 +255,9 @@ func TestSignSSHRequest_Validate(t *testing.T) { func Test_SSHSign(t *testing.T) { user, err := getSignedUserCertificate() - assert.FatalError(t, err) + require.NoError(t, err) host, err := getSignedHostCertificate() - assert.FatalError(t, err) + require.NoError(t, err) userB64 := base64.StdEncoding.EncodeToString(user.Marshal()) hostB64 := base64.StdEncoding.EncodeToString(host.Marshal()) @@ -264,24 +266,24 @@ func Test_SSHSign(t *testing.T) { PublicKey: user.Key.Marshal(), OTT: "ott", }) - assert.FatalError(t, err) + require.NoError(t, err) hostReq, err := json.Marshal(SSHSignRequest{ PublicKey: host.Key.Marshal(), OTT: "ott", }) - assert.FatalError(t, err) + require.NoError(t, err) userAddReq, err := json.Marshal(SSHSignRequest{ PublicKey: user.Key.Marshal(), OTT: "ott", AddUserPublicKey: user.Key.Marshal(), }) - assert.FatalError(t, err) + require.NoError(t, err) userIdentityReq, err := json.Marshal(SSHSignRequest{ PublicKey: user.Key.Marshal(), OTT: "ott", IdentityCSR: CertificateRequest{parseCertificateRequest(csrPEM)}, }) - assert.FatalError(t, err) + require.NoError(t, err) identityCerts := []*x509.Certificate{ parseCertificate(certPEM), } @@ -355,11 +357,11 @@ func Test_SSHSign(t *testing.T) { func Test_SSHRoots(t *testing.T) { user, err := ssh.NewPublicKey(sshUserKey.Public()) - assert.FatalError(t, err) + require.NoError(t, err) userB64 := base64.StdEncoding.EncodeToString(user.Marshal()) host, err := ssh.NewPublicKey(sshHostKey.Public()) - assert.FatalError(t, err) + require.NoError(t, err) hostB64 := base64.StdEncoding.EncodeToString(host.Marshal()) tests := []struct { @@ -409,11 +411,11 @@ func Test_SSHRoots(t *testing.T) { func Test_SSHFederation(t *testing.T) { user, err := ssh.NewPublicKey(sshUserKey.Public()) - assert.FatalError(t, err) + require.NoError(t, err) userB64 := base64.StdEncoding.EncodeToString(user.Marshal()) host, err := ssh.NewPublicKey(sshHostKey.Public()) - assert.FatalError(t, err) + require.NoError(t, err) hostB64 := base64.StdEncoding.EncodeToString(host.Marshal()) tests := []struct { @@ -471,9 +473,9 @@ func Test_SSHConfig(t *testing.T) { {Name: "ca.tpl", Type: templates.File, Comment: "#", Path: "/etc/ssh/ca.pub", Content: []byte("ecdsa-sha2-nistp256 AAAA...=")}, } userJSON, err := json.Marshal(userOutput) - assert.FatalError(t, err) + require.NoError(t, err) hostJSON, err := json.Marshal(hostOutput) - assert.FatalError(t, err) + require.NoError(t, err) tests := []struct { name string @@ -574,7 +576,7 @@ func Test_SSHGetHosts(t *testing.T) { {HostID: "2", HostTags: []authority.HostTag{{ID: "1", Name: "group", Value: "1"}, {ID: "2", Name: "group", Value: "2"}}, Hostname: "host2"}, } hostsJSON, err := json.Marshal(hosts) - assert.FatalError(t, err) + require.NoError(t, err) tests := []struct { name string @@ -676,7 +678,7 @@ func Test_SSHBastion(t *testing.T) { func TestSSHPublicKey_MarshalJSON(t *testing.T) { key, err := ssh.NewPublicKey(sshUserKey.Public()) - assert.FatalError(t, err) + require.NoError(t, err) keyB64 := base64.StdEncoding.EncodeToString(key.Marshal()) tests := []struct { @@ -705,7 +707,7 @@ func TestSSHPublicKey_MarshalJSON(t *testing.T) { func TestSSHPublicKey_UnmarshalJSON(t *testing.T) { key, err := ssh.NewPublicKey(sshUserKey.Public()) - assert.FatalError(t, err) + require.NoError(t, err) keyB64 := base64.StdEncoding.EncodeToString(key.Marshal()) type args struct { @@ -736,3 +738,98 @@ func TestSSHPublicKey_UnmarshalJSON(t *testing.T) { }) } } + +func Test_identityModifier_Enforce(t *testing.T) { + now := time.Now() + type fields struct { + Identity *url.URL + NotBefore time.Time + NotAfter time.Time + } + type args struct { + cert *x509.Certificate + } + tests := []struct { + name string + fields fields + args args + want *x509.Certificate + assertion assert.ErrorAssertionFunc + }{ + {"ok", fields{&url.URL{Scheme: "urn", Opaque: "uuid:0c4670b2-d9f1-42bb-9045-184836f16733"}, now, now.Add(time.Hour)}, + args{&x509.Certificate{}}, &x509.Certificate{ + NotBefore: now, + NotAfter: now.Add(time.Hour), + URIs: []*url.URL{{Scheme: "urn", Opaque: "uuid:0c4670b2-d9f1-42bb-9045-184836f16733"}}, + }, assert.NoError}, + {"ok exists", fields{&url.URL{Scheme: "urn", Opaque: "uuid:0c4670b2-d9f1-42bb-9045-184836f16733"}, now, now.Add(time.Hour)}, + args{&x509.Certificate{ + URIs: []*url.URL{{Scheme: "urn", Opaque: "uuid:0c4670b2-d9f1-42bb-9045-184836f16733"}}, + }}, &x509.Certificate{ + NotBefore: now, + NotAfter: now.Add(time.Hour), + URIs: []*url.URL{{Scheme: "urn", Opaque: "uuid:0c4670b2-d9f1-42bb-9045-184836f16733"}}, + }, assert.NoError}, + {"ok append", fields{&url.URL{Scheme: "urn", Opaque: "uuid:0c4670b2-d9f1-42bb-9045-184836f16733"}, now, now.Add(time.Hour)}, + args{&x509.Certificate{ + URIs: []*url.URL{{Scheme: "urn", Opaque: "uuid:27bb66db-e12a-4ff6-9161-aa6b0a98f914"}}, + }}, &x509.Certificate{ + NotBefore: now, + NotAfter: now.Add(time.Hour), + URIs: []*url.URL{ + {Scheme: "urn", Opaque: "uuid:27bb66db-e12a-4ff6-9161-aa6b0a98f914"}, + {Scheme: "urn", Opaque: "uuid:0c4670b2-d9f1-42bb-9045-184836f16733"}, + }, + }, assert.NoError}, + {"ok no identity", fields{nil, now, now.Add(time.Hour)}, + args{&x509.Certificate{}}, &x509.Certificate{ + NotBefore: now, + NotAfter: now.Add(time.Hour), + }, assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &identityModifier{ + Identity: tt.fields.Identity, + NotBefore: tt.fields.NotBefore, + NotAfter: tt.fields.NotAfter, + } + tt.assertion(t, m.Enforce(tt.args.cert)) + }) + } +} + +func Test_getIdentityURI(t *testing.T) { + id, err := uuid.Parse("54a2ec9d-a7d9-4b53-8f9a-efcd275e35e1") + require.NoError(t, err) + u, err := url.Parse(id.URN()) + require.NoError(t, err) + + type args struct { + cr *x509.CertificateRequest + } + tests := []struct { + name string + args args + want *url.URL + }{ + {"ok", args{&x509.CertificateRequest{ + URIs: []*url.URL{u}, + }}, &url.URL{Scheme: "urn", Opaque: "uuid:54a2ec9d-a7d9-4b53-8f9a-efcd275e35e1"}}, + {"ok multiple", args{&x509.CertificateRequest{ + URIs: []*url.URL{u, {Scheme: "urn", Opaque: "uuid:f0e74f3a-95fe-4cf6-98e3-68e55b69ba48"}}, + }}, &url.URL{Scheme: "urn", Opaque: "uuid:54a2ec9d-a7d9-4b53-8f9a-efcd275e35e1"}}, + {"ok multiple with invalid", args{&x509.CertificateRequest{ + URIs: []*url.URL{{Scheme: "urn", Opaque: "uuid:f0e74f3a+95fe+4cf6+98e3+68e55b69ba48"}, u}, + }}, &url.URL{Scheme: "urn", Opaque: "uuid:54a2ec9d-a7d9-4b53-8f9a-efcd275e35e1"}}, + {"ok missing", args{&x509.CertificateRequest{ + URIs: []*url.URL{{Scheme: "https", Host: "example.com", Path: "/54a2ec9d-a7d9-4b53-8f9a-efcd275e35e1"}}, + }}, nil}, + {"ok empty", args{&x509.CertificateRequest{}}, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, getIdentityURI(tt.args.cr)) + }) + } +} diff --git a/authority/admin/api/acme.go b/authority/admin/api/acme.go index 32f2bdcc..6fc70896 100644 --- a/authority/admin/api/acme.go +++ b/authority/admin/api/acme.go @@ -40,12 +40,12 @@ func requireEABEnabled(next http.HandlerFunc) http.HandlerFunc { acmeProvisioner := prov.GetDetails().GetACME() if acmeProvisioner == nil { - render.Error(w, admin.NewErrorISE("error getting ACME details for provisioner '%s'", prov.GetName())) + render.Error(w, r, admin.NewErrorISE("error getting ACME details for provisioner '%s'", prov.GetName())) return } if !acmeProvisioner.RequireEab { - render.Error(w, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner '%s'", prov.GetName())) + render.Error(w, r, admin.NewError(admin.ErrorBadRequestType, "ACME EAB not enabled for provisioner '%s'", prov.GetName())) return } @@ -69,18 +69,18 @@ func NewACMEAdminResponder() ACMEAdminResponder { } // GetExternalAccountKeys writes the response for the EAB keys GET endpoint -func (h *acmeAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, _ *http.Request) { - render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm")) +func (h *acmeAdminResponder) GetExternalAccountKeys(w http.ResponseWriter, r *http.Request) { + render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm")) } // CreateExternalAccountKey writes the response for the EAB key POST endpoint -func (h *acmeAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, _ *http.Request) { - render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm")) +func (h *acmeAdminResponder) CreateExternalAccountKey(w http.ResponseWriter, r *http.Request) { + render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm")) } // DeleteExternalAccountKey writes the response for the EAB key DELETE endpoint -func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, _ *http.Request) { - render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm")) +func (h *acmeAdminResponder) DeleteExternalAccountKey(w http.ResponseWriter, r *http.Request) { + render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "this functionality is currently only available in Certificate Manager: https://u.step.sm/cm")) } func eakToLinked(k *acme.ExternalAccountKey) *linkedca.EABKey { diff --git a/authority/admin/api/admin.go b/authority/admin/api/admin.go index e4d9d9fe..ce22de05 100644 --- a/authority/admin/api/admin.go +++ b/authority/admin/api/admin.go @@ -90,7 +90,7 @@ func GetAdmin(w http.ResponseWriter, r *http.Request) { adm, ok := mustAuthority(r.Context()).LoadAdminByID(id) if !ok { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "admin %s not found", id)) return } @@ -101,17 +101,17 @@ func GetAdmin(w http.ResponseWriter, r *http.Request) { func GetAdmins(w http.ResponseWriter, r *http.Request) { cursor, limit, err := api.ParseCursor(r) if err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error parsing cursor and limit from query params")) return } admins, nextCursor, err := mustAuthority(r.Context()).GetAdmins(cursor, limit) if err != nil { - render.Error(w, admin.WrapErrorISE(err, "error retrieving paginated admins")) + render.Error(w, r, admin.WrapErrorISE(err, "error retrieving paginated admins")) return } - render.JSON(w, &GetAdminsResponse{ + render.JSON(w, r, &GetAdminsResponse{ Admins: admins, NextCursor: nextCursor, }) @@ -121,19 +121,19 @@ func GetAdmins(w http.ResponseWriter, r *http.Request) { func CreateAdmin(w http.ResponseWriter, r *http.Request) { var body CreateAdminRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body")) return } if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } auth := mustAuthority(r.Context()) p, err := auth.LoadProvisionerByName(body.Provisioner) if err != nil { - render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner)) + render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner)) return } adm := &linkedca.Admin{ @@ -143,7 +143,7 @@ func CreateAdmin(w http.ResponseWriter, r *http.Request) { } // Store to authority collection. if err := auth.StoreAdmin(r.Context(), adm, p); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error storing admin")) + render.Error(w, r, admin.WrapErrorISE(err, "error storing admin")) return } @@ -155,23 +155,23 @@ func DeleteAdmin(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") if err := mustAuthority(r.Context()).RemoveAdmin(r.Context(), id); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error deleting admin %s", id)) + render.Error(w, r, admin.WrapErrorISE(err, "error deleting admin %s", id)) return } - render.JSON(w, &DeleteResponse{Status: "ok"}) + render.JSON(w, r, &DeleteResponse{Status: "ok"}) } // UpdateAdmin updates an existing admin. func UpdateAdmin(w http.ResponseWriter, r *http.Request) { var body UpdateAdminRequest if err := read.JSON(r.Body, &body); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body")) return } if err := body.Validate(); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -179,7 +179,7 @@ func UpdateAdmin(w http.ResponseWriter, r *http.Request) { auth := mustAuthority(r.Context()) adm, err := auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type}) if err != nil { - render.Error(w, admin.WrapErrorISE(err, "error updating admin %s", id)) + render.Error(w, r, admin.WrapErrorISE(err, "error updating admin %s", id)) return } diff --git a/authority/admin/api/middleware.go b/authority/admin/api/middleware.go index fb29219f..68006b7f 100644 --- a/authority/admin/api/middleware.go +++ b/authority/admin/api/middleware.go @@ -1,7 +1,6 @@ package api import ( - "errors" "net/http" "github.com/go-chi/chi/v5" @@ -20,7 +19,7 @@ import ( func requireAPIEnabled(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !mustAuthority(r.Context()).IsAdminAPIEnabled() { - render.Error(w, admin.NewError(admin.ErrorNotImplementedType, "administration API not enabled")) + render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "administration API not enabled")) return } next(w, r) @@ -32,7 +31,7 @@ func extractAuthorizeTokenAdmin(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tok := r.Header.Get("Authorization") if tok == "" { - render.Error(w, admin.NewError(admin.ErrorUnauthorizedType, + render.Error(w, r, admin.NewError(admin.ErrorUnauthorizedType, "missing authorization header token")) return } @@ -40,7 +39,7 @@ func extractAuthorizeTokenAdmin(next http.HandlerFunc) http.HandlerFunc { ctx := r.Context() adm, err := mustAuthority(ctx).AuthorizeAdminToken(r, tok) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -65,13 +64,13 @@ func loadProvisionerByName(next http.HandlerFunc) http.HandlerFunc { // TODO(hs): distinguish 404 vs. 500 if p, err = auth.LoadProvisionerByName(name); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) + render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name)) return } prov, err := adminDB.GetProvisioner(ctx, p.GetID()) if err != nil { - render.Error(w, admin.WrapErrorISE(err, "error retrieving provisioner %s", name)) + render.Error(w, r, admin.WrapErrorISE(err, "error retrieving provisioner %s", name)) return } @@ -92,7 +91,7 @@ func checkAction(next http.HandlerFunc, supportedInStandalone bool) http.Handler // when an action is not supported in standalone mode and when // using a nosql.DB backend, actions are not supported if _, ok := admin.MustFromContext(r.Context()).(*nosql.DB); ok { - render.Error(w, admin.NewError(admin.ErrorNotImplementedType, + render.Error(w, r, admin.NewError(admin.ErrorNotImplementedType, "operation not supported in standalone mode")) return } @@ -125,16 +124,16 @@ func loadExternalAccountKey(next http.HandlerFunc) http.HandlerFunc { } if err != nil { - if errors.Is(err, acme.ErrNotFound) { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found")) + if acme.IsErrNotFound(err) { + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found")) return } - render.Error(w, admin.WrapErrorISE(err, "error retrieving ACME External Account Key")) + render.Error(w, r, admin.WrapErrorISE(err, "error retrieving ACME External Account Key")) return } if eak == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key not found")) return } diff --git a/authority/admin/api/policy.go b/authority/admin/api/policy.go index 89744893..d2d8183f 100644 --- a/authority/admin/api/policy.go +++ b/authority/admin/api/policy.go @@ -35,7 +35,7 @@ type PolicyAdminResponder interface { // policyAdminResponder implements PolicyAdminResponder. type policyAdminResponder struct{} -// NewACMEAdminResponder returns a new PolicyAdminResponder. +// NewPolicyAdminResponder returns a new PolicyAdminResponder. func NewPolicyAdminResponder() PolicyAdminResponder { return &policyAdminResponder{} } @@ -44,7 +44,7 @@ func NewPolicyAdminResponder() PolicyAdminResponder { func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -52,12 +52,12 @@ func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *ht authorityPolicy, err := auth.GetAuthorityPolicy(r.Context()) var ae *admin.Error if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { - render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy")) + render.Error(w, r, admin.WrapErrorISE(ae, "error retrieving authority policy")) return } if authorityPolicy == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist")) return } @@ -68,7 +68,7 @@ func (par *policyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *ht func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -77,26 +77,26 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r var ae *admin.Error if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { - render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error retrieving authority policy")) return } if authorityPolicy != nil { adminErr := admin.NewError(admin.ErrorConflictType, "authority already has a policy") - render.Error(w, adminErr) + render.Error(w, r, adminErr) return } var newPolicy = new(linkedca.Policy) if err := read.ProtoJSON(r.Body, newPolicy); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } newPolicy.Deduplicate() if err := validatePolicy(newPolicy); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy")) return } @@ -105,11 +105,11 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r var createdPolicy *linkedca.Policy if createdPolicy, err = auth.CreateAuthorityPolicy(ctx, adm, newPolicy); err != nil { if isBadRequest(err) { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error storing authority policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error storing authority policy")) return } - render.Error(w, admin.WrapErrorISE(err, "error storing authority policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error storing authority policy")) return } @@ -120,7 +120,7 @@ func (par *policyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -129,25 +129,25 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r var ae *admin.Error if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { - render.Error(w, admin.WrapErrorISE(err, "error retrieving authority policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error retrieving authority policy")) return } if authorityPolicy == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist")) return } var newPolicy = new(linkedca.Policy) if err := read.ProtoJSON(r.Body, newPolicy); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } newPolicy.Deduplicate() if err := validatePolicy(newPolicy); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating authority policy")) return } @@ -156,11 +156,11 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r var updatedPolicy *linkedca.Policy if updatedPolicy, err = auth.UpdateAuthorityPolicy(ctx, adm, newPolicy); err != nil { if isBadRequest(err) { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating authority policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating authority policy")) return } - render.Error(w, admin.WrapErrorISE(err, "error updating authority policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error updating authority policy")) return } @@ -171,7 +171,7 @@ func (par *policyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -180,35 +180,35 @@ func (par *policyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r var ae *admin.Error if errors.As(err, &ae) && !ae.IsType(admin.ErrorNotFoundType) { - render.Error(w, admin.WrapErrorISE(ae, "error retrieving authority policy")) + render.Error(w, r, admin.WrapErrorISE(ae, "error retrieving authority policy")) return } if authorityPolicy == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "authority policy does not exist")) return } if err := auth.RemoveAuthorityPolicy(ctx); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error deleting authority policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error deleting authority policy")) return } - render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) + render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK) } // GetProvisionerPolicy handles the GET /admin/provisioners/{name}/policy request func (par *policyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } prov := linkedca.MustProvisionerFromContext(ctx) provisionerPolicy := prov.GetPolicy() if provisionerPolicy == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist")) return } @@ -219,7 +219,7 @@ func (par *policyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r * func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -227,20 +227,20 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, provisionerPolicy := prov.GetPolicy() if provisionerPolicy != nil { adminErr := admin.NewError(admin.ErrorConflictType, "provisioner %s already has a policy", prov.Name) - render.Error(w, adminErr) + render.Error(w, r, adminErr) return } var newPolicy = new(linkedca.Policy) if err := read.ProtoJSON(r.Body, newPolicy); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } newPolicy.Deduplicate() if err := validatePolicy(newPolicy); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy")) return } @@ -248,11 +248,11 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, auth := mustAuthority(ctx) if err := auth.UpdateProvisioner(ctx, prov); err != nil { if isBadRequest(err) { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner policy")) return } - render.Error(w, admin.WrapErrorISE(err, "error creating provisioner policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error creating provisioner policy")) return } @@ -263,27 +263,27 @@ func (par *policyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } prov := linkedca.MustProvisionerFromContext(ctx) provisionerPolicy := prov.GetPolicy() if provisionerPolicy == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist")) return } var newPolicy = new(linkedca.Policy) if err := read.ProtoJSON(r.Body, newPolicy); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } newPolicy.Deduplicate() if err := validatePolicy(newPolicy); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating provisioner policy")) return } @@ -291,11 +291,11 @@ func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter, auth := mustAuthority(ctx) if err := auth.UpdateProvisioner(ctx, prov); err != nil { if isBadRequest(err) { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner policy")) return } - render.Error(w, admin.WrapErrorISE(err, "error updating provisioner policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error updating provisioner policy")) return } @@ -306,13 +306,13 @@ func (par *policyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter, func (par *policyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } prov := linkedca.MustProvisionerFromContext(ctx) if prov.Policy == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "provisioner policy does not exist")) return } @@ -321,24 +321,24 @@ func (par *policyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter, auth := mustAuthority(ctx) if err := auth.UpdateProvisioner(ctx, prov); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error deleting provisioner policy")) return } - render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) + render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK) } func (par *policyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } eak := linkedca.MustExternalAccountKeyFromContext(ctx) eakPolicy := eak.GetPolicy() if eakPolicy == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist")) return } @@ -348,7 +348,7 @@ func (par *policyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r * func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -357,20 +357,20 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, eakPolicy := eak.GetPolicy() if eakPolicy != nil { adminErr := admin.NewError(admin.ErrorConflictType, "ACME EAK %s already has a policy", eak.Id) - render.Error(w, adminErr) + render.Error(w, r, adminErr) return } var newPolicy = new(linkedca.Policy) if err := read.ProtoJSON(r.Body, newPolicy); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } newPolicy.Deduplicate() if err := validatePolicy(newPolicy); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy")) return } @@ -379,7 +379,7 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, acmeEAK := linkedEAKToCertificates(eak) acmeDB := acme.MustDatabaseFromContext(ctx) if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error creating ACME EAK policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error creating ACME EAK policy")) return } @@ -389,7 +389,7 @@ func (par *policyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -397,20 +397,20 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, eak := linkedca.MustExternalAccountKeyFromContext(ctx) eakPolicy := eak.GetPolicy() if eakPolicy == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist")) return } var newPolicy = new(linkedca.Policy) if err := read.ProtoJSON(r.Body, newPolicy); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } newPolicy.Deduplicate() if err := validatePolicy(newPolicy); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error validating ACME EAK policy")) return } @@ -418,7 +418,7 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, acmeEAK := linkedEAKToCertificates(eak) acmeDB := acme.MustDatabaseFromContext(ctx) if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error updating ACME EAK policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error updating ACME EAK policy")) return } @@ -428,7 +428,7 @@ func (par *policyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if err := blockLinkedCA(ctx); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -436,7 +436,7 @@ func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, eak := linkedca.MustExternalAccountKeyFromContext(ctx) eakPolicy := eak.GetPolicy() if eakPolicy == nil { - render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist")) + render.Error(w, r, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist")) return } @@ -446,11 +446,11 @@ func (par *policyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, acmeEAK := linkedEAKToCertificates(eak) acmeDB := acme.MustDatabaseFromContext(ctx) if err := acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error deleting ACME EAK policy")) + render.Error(w, r, admin.WrapErrorISE(err, "error deleting ACME EAK policy")) return } - render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) + render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK) } // blockLinkedCA blocks all API operations on linked deployments diff --git a/authority/admin/api/provisioner.go b/authority/admin/api/provisioner.go index 709399dd..b2a59cfa 100644 --- a/authority/admin/api/provisioner.go +++ b/authority/admin/api/provisioner.go @@ -40,19 +40,19 @@ func GetProvisioner(w http.ResponseWriter, r *http.Request) { if id != "" { if p, err = auth.LoadProvisionerByID(id); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id)) + render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", id)) return } } else { if p, err = auth.LoadProvisionerByName(name); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) + render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name)) return } } prov, err := db.GetProvisioner(ctx, p.GetID()) if err != nil { - render.Error(w, err) + render.Error(w, r, err) return } render.ProtoJSON(w, prov) @@ -62,17 +62,17 @@ func GetProvisioner(w http.ResponseWriter, r *http.Request) { func GetProvisioners(w http.ResponseWriter, r *http.Request) { cursor, limit, err := api.ParseCursor(r) if err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error parsing cursor and limit from query params")) return } p, next, err := mustAuthority(r.Context()).GetProvisioners(cursor, limit) if err != nil { - render.Error(w, errs.InternalServerErr(err)) + render.Error(w, r, errs.InternalServerErr(err)) return } - render.JSON(w, &GetProvisionersResponse{ + render.JSON(w, r, &GetProvisionersResponse{ Provisioners: p, NextCursor: next, }) @@ -82,24 +82,24 @@ func GetProvisioners(w http.ResponseWriter, r *http.Request) { func CreateProvisioner(w http.ResponseWriter, r *http.Request) { var prov = new(linkedca.Provisioner) if err := read.ProtoJSON(r.Body, prov); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } // TODO: Validate inputs if err := authority.ValidateClaims(prov.Claims); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } // validate the templates and template data if err := validateTemplates(prov.X509Template, prov.SshTemplate); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template")) return } if err := mustAuthority(r.Context()).StoreProvisioner(r.Context(), prov); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name)) + render.Error(w, r, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name)) return } render.ProtoJSONStatus(w, prov, http.StatusCreated) @@ -118,29 +118,29 @@ func DeleteProvisioner(w http.ResponseWriter, r *http.Request) { if id != "" { if p, err = auth.LoadProvisionerByID(id); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id)) + render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", id)) return } } else { if p, err = auth.LoadProvisionerByName(name); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) + render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner %s", name)) return } } if err := auth.RemoveProvisioner(r.Context(), p.GetID()); err != nil { - render.Error(w, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName())) + render.Error(w, r, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName())) return } - render.JSON(w, &DeleteResponse{Status: "ok"}) + render.JSON(w, r, &DeleteResponse{Status: "ok"}) } // UpdateProvisioner updates an existing prov. func UpdateProvisioner(w http.ResponseWriter, r *http.Request) { var nu = new(linkedca.Provisioner) if err := read.ProtoJSON(r.Body, nu); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -151,51 +151,51 @@ func UpdateProvisioner(w http.ResponseWriter, r *http.Request) { p, err := auth.LoadProvisionerByName(name) if err != nil { - render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name)) + render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name)) return } old, err := db.GetProvisioner(r.Context(), p.GetID()) if err != nil { - render.Error(w, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", p.GetID())) + render.Error(w, r, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", p.GetID())) return } if nu.Id != old.Id { - render.Error(w, admin.NewErrorISE("cannot change provisioner ID")) + render.Error(w, r, admin.NewErrorISE("cannot change provisioner ID")) return } if nu.Type != old.Type { - render.Error(w, admin.NewErrorISE("cannot change provisioner type")) + render.Error(w, r, admin.NewErrorISE("cannot change provisioner type")) return } if nu.AuthorityId != old.AuthorityId { - render.Error(w, admin.NewErrorISE("cannot change provisioner authorityID")) + render.Error(w, r, admin.NewErrorISE("cannot change provisioner authorityID")) return } if !nu.CreatedAt.AsTime().Equal(old.CreatedAt.AsTime()) { - render.Error(w, admin.NewErrorISE("cannot change provisioner createdAt")) + render.Error(w, r, admin.NewErrorISE("cannot change provisioner createdAt")) return } if !nu.DeletedAt.AsTime().Equal(old.DeletedAt.AsTime()) { - render.Error(w, admin.NewErrorISE("cannot change provisioner deletedAt")) + render.Error(w, r, admin.NewErrorISE("cannot change provisioner deletedAt")) return } // TODO: Validate inputs if err := authority.ValidateClaims(nu.Claims); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } // validate the templates and template data if err := validateTemplates(nu.X509Template, nu.SshTemplate); err != nil { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "invalid template")) return } if err := auth.UpdateProvisioner(r.Context(), nu); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } render.ProtoJSON(w, nu) diff --git a/authority/admin/api/webhook.go b/authority/admin/api/webhook.go index f01ddb65..04255e15 100644 --- a/authority/admin/api/webhook.go +++ b/authority/admin/api/webhook.go @@ -71,28 +71,28 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter var newWebhook = new(linkedca.Webhook) if err := read.ProtoJSON(r.Body, newWebhook); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if err := validateWebhook(newWebhook); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if newWebhook.Secret != "" { err := admin.NewError(admin.ErrorBadRequestType, "webhook secret must not be set") - render.Error(w, err) + render.Error(w, r, err) return } if newWebhook.Id != "" { err := admin.NewError(admin.ErrorBadRequestType, "webhook ID must not be set") - render.Error(w, err) + render.Error(w, r, err) return } id, err := randutil.UUIDv4() if err != nil { - render.Error(w, admin.WrapErrorISE(err, "error generating webhook id")) + render.Error(w, r, admin.WrapErrorISE(err, "error generating webhook id")) return } newWebhook.Id = id @@ -101,14 +101,14 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter 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) + render.Error(w, r, err) return } } secret, err := randutil.Bytes(64) if err != nil { - render.Error(w, admin.WrapErrorISE(err, "error generating webhook secret")) + render.Error(w, r, admin.WrapErrorISE(err, "error generating webhook secret")) return } newWebhook.Secret = base64.StdEncoding.EncodeToString(secret) @@ -117,11 +117,11 @@ func (war *webhookAdminResponder) CreateProvisionerWebhook(w http.ResponseWriter if err := auth.UpdateProvisioner(ctx, prov); err != nil { if isBadRequest(err) { - render.Error(w, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner webhook")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error creating provisioner webhook")) return } - render.Error(w, admin.WrapErrorISE(err, "error creating provisioner webhook")) + render.Error(w, r, admin.WrapErrorISE(err, "error creating provisioner webhook")) return } @@ -145,21 +145,21 @@ func (war *webhookAdminResponder) DeleteProvisionerWebhook(w http.ResponseWriter } } if !found { - render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) + render.JSONStatus(w, r, 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")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error deleting provisioner webhook")) return } - render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner webhook")) + render.Error(w, r, admin.WrapErrorISE(err, "error deleting provisioner webhook")) return } - render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) + render.JSONStatus(w, r, DeleteResponse{Status: "ok"}, http.StatusOK) } func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter, r *http.Request) { @@ -170,12 +170,12 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter var newWebhook = new(linkedca.Webhook) if err := read.ProtoJSON(r.Body, newWebhook); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } if err := validateWebhook(newWebhook); err != nil { - render.Error(w, err) + render.Error(w, r, err) return } @@ -186,13 +186,13 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter } if newWebhook.Secret != "" && newWebhook.Secret != wh.Secret { err := admin.NewError(admin.ErrorBadRequestType, "webhook secret cannot be updated") - render.Error(w, err) + render.Error(w, r, 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) + render.Error(w, r, err) return } newWebhook.Id = wh.Id @@ -203,17 +203,17 @@ func (war *webhookAdminResponder) UpdateProvisionerWebhook(w http.ResponseWriter 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) + render.Error(w, r, 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")) + render.Error(w, r, admin.WrapError(admin.ErrorBadRequestType, err, "error updating provisioner webhook")) return } - render.Error(w, admin.WrapErrorISE(err, "error updating provisioner webhook")) + render.Error(w, r, admin.WrapErrorISE(err, "error updating provisioner webhook")) return } diff --git a/authority/admin/errors.go b/authority/admin/errors.go index c729c8b2..a14e2dee 100644 --- a/authority/admin/errors.go +++ b/authority/admin/errors.go @@ -205,8 +205,8 @@ func (e *Error) ToLog() (interface{}, error) { } // Render implements render.RenderableError for Error. -func (e *Error) Render(w http.ResponseWriter) { +func (e *Error) Render(w http.ResponseWriter, r *http.Request) { e.Message = e.Err.Error() - render.JSONStatus(w, e, e.StatusCode()) + render.JSONStatus(w, r, e, e.StatusCode()) } diff --git a/authority/authority.go b/authority/authority.go index c112bc25..c301b28b 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -62,9 +62,10 @@ type Authority struct { x509Enforcers []provisioner.CertificateEnforcer // SCEP CA - scepOptions *scep.Options - validateSCEP bool - scepAuthority *scep.Authority + scepOptions *scep.Options + validateSCEP bool + scepAuthority *scep.Authority + scepKeyManager provisioner.SCEPKeyManager // SSH CA sshHostPassword []byte @@ -139,7 +140,7 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) { } } if a.keyManager != nil { - a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter} + a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter) } if !a.skipInit { @@ -168,7 +169,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) { } } if a.keyManager != nil { - a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter} + a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter) } // Validate required options @@ -255,7 +256,10 @@ func (a *Authority) ReloadAdminResources(ctx context.Context) error { provClxn := provisioner.NewCollection(provisionerConfig.Audiences) for _, p := range provList { if err := p.Init(provisionerConfig); err != nil { - return err + log.Printf("failed to initialize %s provisioner %q: %v\n", p.GetType(), p.GetName(), err) + p = provisioner.Uninitialized{ + Interface: p, Reason: err, + } } if err := provClxn.Store(p); err != nil { return err @@ -349,7 +353,7 @@ func (a *Authority) init() error { return err } - a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter} + a.keyManager = newInstrumentedKeyManager(a.keyManager, a.meter) } // Initialize linkedca client if necessary. On a linked RA, the issuer @@ -446,6 +450,7 @@ func (a *Authority) init() error { return err } a.rootX509Certs = append(a.rootX509Certs, resp.RootCertificate) + a.intermediateX509Certs = append(a.intermediateX509Certs, resp.IntermediateCertificates...) } } @@ -694,32 +699,42 @@ func (a *Authority) init() error { options := &scep.Options{ Roots: a.rootX509Certs, Intermediates: a.intermediateX509Certs, - SignerCert: a.intermediateX509Certs[0], } - if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKey: a.config.IntermediateKey, - Password: a.password, - }); err != nil { - return err + + // intermediate certificates can be empty in RA mode + if len(a.intermediateX509Certs) > 0 { + options.SignerCert = a.intermediateX509Certs[0] } - // TODO(hs): instead of creating the decrypter here, pass the - // intermediate key + chain down to the SCEP authority, - // and only instantiate it when required there. Is that possible? - // Also with entering passwords? - // TODO(hs): if moving the logic, try improving the logic for the - // decrypter password too? Right now it needs to be entered multiple - // times; I've observed it to be three times maximum, every time - // the intermediate key is read. - _, isRSA := options.Signer.Public().(*rsa.PublicKey) - if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSA { - if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: a.config.IntermediateKey, - Password: a.password, - }); err == nil { - // only pass the decrypter down when it was successfully created, - // meaning it's an RSA key, and `CreateDecrypter` did not fail. - options.Decrypter = decrypter - options.DecrypterCert = options.Intermediates[0] + + // attempt to create the (default) SCEP signer if the intermediate + // key is configured. + if a.config.IntermediateKey != "" { + if options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ + SigningKey: a.config.IntermediateKey, + Password: a.password, + }); err != nil { + return err + } + + // TODO(hs): instead of creating the decrypter here, pass the + // intermediate key + chain down to the SCEP authority, + // and only instantiate it when required there. Is that possible? + // Also with entering passwords? + // TODO(hs): if moving the logic, try improving the logic for the + // decrypter password too? Right now it needs to be entered multiple + // times; I've observed it to be three times maximum, every time + // the intermediate key is read. + _, isRSAKey := options.Signer.Public().(*rsa.PublicKey) + if km, ok := a.keyManager.(kmsapi.Decrypter); ok && isRSAKey { + if decrypter, err := km.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: a.config.IntermediateKey, + Password: a.password, + }); err == nil { + // only pass the decrypter down when it was successfully created, + // meaning it's an RSA key, and `CreateDecrypter` did not fail. + options.Decrypter = decrypter + options.DecrypterCert = options.Intermediates[0] + } } } diff --git a/authority/authority_test.go b/authority/authority_test.go index 3787dab7..387f7beb 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -69,6 +69,15 @@ func testAuthority(t *testing.T, opts ...Option) *Authority { EnableSSHCA: &enableSSHCA, }, }, + &provisioner.JWK{ + Name: "uninitialized", + Type: "JWK", + Key: clijwk, + Claims: &provisioner.Claims{ + MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, + MaxTLSDur: &provisioner.Duration{Duration: time.Minute}, + }, + }, } c := &Config{ Address: "127.0.0.1:443", @@ -113,7 +122,7 @@ func TestAuthorityNew(t *testing.T) { c.Root = []string{"foo"} return &newTest{ config: c, - err: errors.New("error reading foo: no such file or directory"), + err: errors.New(`error reading "foo": no such file or directory`), } }, "fail bad password": func(t *testing.T) *newTest { @@ -131,7 +140,7 @@ func TestAuthorityNew(t *testing.T) { c.IntermediateCert = "wrong" return &newTest{ config: c, - err: errors.New("error reading wrong: no such file or directory"), + err: errors.New(`error reading "wrong": no such file or directory`), } }, } diff --git a/authority/authorize.go b/authority/authorize.go index 02147687..0a693c64 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -64,6 +64,10 @@ func (a *Authority) getProvisionerFromToken(token string) (provisioner.Interface if !ok { return nil, nil, fmt.Errorf("provisioner not found or invalid audience (%s)", strings.Join(claims.Audience, ", ")) } + // If the provisioner is disabled, send an appropriate message to the client + if _, ok := p.(provisioner.Uninitialized); ok { + return nil, nil, errs.New(http.StatusUnauthorized, "provisioner %q is disabled due to an initialization error", p.GetName()) + } return p, &claims, nil } diff --git a/authority/authorize_test.go b/authority/authorize_test.go index 551fd95a..f7287e7a 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -24,6 +24,7 @@ import ( "go.step.sm/crypto/randutil" "go.step.sm/crypto/x509util" + "github.com/google/uuid" "github.com/smallstep/assert" "github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/authority/provisioner" @@ -337,6 +338,24 @@ func TestAuthority_authorizeToken(t *testing.T) { code: http.StatusUnauthorized, } }, + "fail/uninitialized": func(t *testing.T) *authorizeTest { + cl := jose.Claims{ + Subject: "test.smallstep.com", + Issuer: "uninitialized", + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), + Audience: validAudience, + ID: uuid.NewString(), + } + raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() + assert.FatalError(t, err) + return &authorizeTest{ + auth: a, + token: raw, + err: errors.New(`provisioner "uninitialized" is disabled due to an initialization error`), + code: http.StatusUnauthorized, + } + }, } for name, genTestCase := range tests { diff --git a/authority/linkedca.go b/authority/linkedca.go index 3eaa76c9..aa8de3a8 100644 --- a/authority/linkedca.go +++ b/authority/linkedca.go @@ -110,7 +110,7 @@ func newLinkedCAClient(token string) (*linkedCaClient, error) { tlsConfig.GetClientCertificate = renewer.GetClientCertificate // Start mTLS client - conn, err := grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) + conn, err := grpc.NewClient(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) if err != nil { return nil, errors.Wrapf(err, "error connecting %s", u.Host) } @@ -478,10 +478,7 @@ func getAuthority(sans []string) (string, error) { // getRootCertificate creates an insecure majordomo client and returns the // verified root certificate. func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ //nolint:gosec // used in bootstrap protocol InsecureSkipVerify: true, // lgtm[go/disabled-certificate-check] }))) @@ -489,7 +486,7 @@ func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error) return nil, errors.Wrapf(err, "error connecting %s", endpoint) } - ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client := linkedca.NewMajordomoClient(conn) @@ -531,11 +528,7 @@ func getRootCertificate(endpoint, fingerprint string) (*x509.Certificate, error) // login creates a new majordomo client with just the root ca pool and returns // the signed certificate and tls configuration. func login(authority, token string, csr *x509.CertificateRequest, signer crypto.PrivateKey, endpoint string, rootCAs *x509.CertPool) (*tls.Certificate, *tls.Config, error) { - // Connect to majordomo - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - conn, err := grpc.DialContext(ctx, endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ MinVersion: tls.VersionTLS12, RootCAs: rootCAs, }))) @@ -544,7 +537,7 @@ func login(authority, token string, csr *x509.CertificateRequest, signer crypto. } // Login to get the signed certificate - ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() client := linkedca.NewMajordomoClient(conn) diff --git a/authority/meter.go b/authority/meter.go index cccda22a..c99069a4 100644 --- a/authority/meter.go +++ b/authority/meter.go @@ -66,6 +66,22 @@ type instrumentedKeyManager struct { meter Meter } +type instrumentedKeyAndDecrypterManager struct { + kms.KeyManager + decrypter kmsapi.Decrypter + meter Meter +} + +func newInstrumentedKeyManager(k kms.KeyManager, m Meter) kms.KeyManager { + decrypter, isDecrypter := k.(kmsapi.Decrypter) + switch { + case isDecrypter: + return &instrumentedKeyAndDecrypterManager{&instrumentedKeyManager{k, m}, decrypter, m} + default: + return &instrumentedKeyManager{k, m} + } +} + func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (s crypto.Signer, err error) { if s, err = i.KeyManager.CreateSigner(req); err == nil { s = &instrumentedKMSSigner{s, i.meter} @@ -74,6 +90,10 @@ func (i *instrumentedKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) ( return } +func (i *instrumentedKeyAndDecrypterManager) CreateDecrypter(req *kmsapi.CreateDecrypterRequest) (s crypto.Decrypter, err error) { + return i.decrypter.CreateDecrypter(req) +} + type instrumentedKMSSigner struct { crypto.Signer meter Meter @@ -85,3 +105,7 @@ func (i *instrumentedKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto. return } + +var _ kms.KeyManager = (*instrumentedKeyManager)(nil) +var _ kms.KeyManager = (*instrumentedKeyAndDecrypterManager)(nil) +var _ kmsapi.Decrypter = (*instrumentedKeyAndDecrypterManager)(nil) diff --git a/authority/options.go b/authority/options.go index 55c27321..9738b391 100644 --- a/authority/options.go +++ b/authority/options.go @@ -226,6 +226,16 @@ func WithFullSCEPOptions(options *scep.Options) Option { } } +// WithSCEPKeyManager defines the key manager used on SCEP provisioners. +// +// This feature is EXPERIMENTAL and might change at any time. +func WithSCEPKeyManager(skm provisioner.SCEPKeyManager) Option { + return func(a *Authority) error { + a.scepKeyManager = skm + return nil + } +} + // WithSSHUserSigner defines the signer used to sign SSH user certificates. func WithSSHUserSigner(s crypto.Signer) Option { return func(a *Authority) error { diff --git a/authority/policy/policy.go b/authority/policy/policy.go index 96c7d7ea..1da45e83 100644 --- a/authority/policy/policy.go +++ b/authority/policy/policy.go @@ -21,6 +21,7 @@ type HostPolicy policy.SSHNamePolicyEngine func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy, error) { // return early if no policy engine options to configure if policyOptions == nil { + //nolint:nilnil,nolintlint // expected values return nil, nil } @@ -50,6 +51,7 @@ func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy, // ensure no policy engine is returned when no name options were provided if len(options) == 0 { + //nolint:nilnil,nolintlint // expected values return nil, nil } @@ -93,6 +95,7 @@ func NewSSHHostPolicyEngine(policyOptions SSHPolicyOptionsInterface) (HostPolicy func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEngineType) (policy.SSHNamePolicyEngine, error) { // return early if no policy engine options to configure if policyOptions == nil { + //nolint:nilnil,nolintlint // expected values return nil, nil } @@ -134,6 +137,7 @@ func newSSHPolicyEngine(policyOptions SSHPolicyOptionsInterface, typ sshPolicyEn // ensure no policy engine is returned when no name options were provided if len(options) == 0 { + //nolint:nilnil,nolintlint // expected values return nil, nil } diff --git a/authority/provisioner/aws_certificates.pem b/authority/provisioner/aws_certificates.pem index 994758b9..167007ff 100644 --- a/authority/provisioner/aws_certificates.pem +++ b/authority/provisioner/aws_certificates.pem @@ -1,25 +1,89 @@ # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-signature.html # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/regions-certs.html use RSA format -# default certificate for "other regions" + +# certificate for us-east-2 -----BEGIN CERTIFICATE----- -MIIDIjCCAougAwIBAgIJAKnL4UEDMN/FMA0GCSqGSIb3DQEBBQUAMGoxCzAJBgNV -BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRgw -FgYDVQQKEw9BbWF6b24uY29tIEluYy4xGjAYBgNVBAMTEWVjMi5hbWF6b25hd3Mu -Y29tMB4XDTE0MDYwNTE0MjgwMloXDTI0MDYwNTE0MjgwMlowajELMAkGA1UEBhMC -VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxGDAWBgNV -BAoTD0FtYXpvbi5jb20gSW5jLjEaMBgGA1UEAxMRZWMyLmFtYXpvbmF3cy5jb20w -gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAIe9GN//SRK2knbjySG0ho3yqQM3 -e2TDhWO8D2e8+XZqck754gFSo99AbT2RmXClambI7xsYHZFapbELC4H91ycihvrD -jbST1ZjkLQgga0NE1q43eS68ZeTDccScXQSNivSlzJZS8HJZjgqzBlXjZftjtdJL -XeE4hwvo0sD4f3j9AgMBAAGjgc8wgcwwHQYDVR0OBBYEFCXWzAgVyrbwnFncFFIs -77VBdlE4MIGcBgNVHSMEgZQwgZGAFCXWzAgVyrbwnFncFFIs77VBdlE4oW6kbDBq -MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2Vh -dHRsZTEYMBYGA1UEChMPQW1hem9uLmNvbSBJbmMuMRowGAYDVQQDExFlYzIuYW1h -em9uYXdzLmNvbYIJAKnL4UEDMN/FMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF -BQADgYEAFYcz1OgEhQBXIwIdsgCOS8vEtiJYF+j9uO6jz7VOmJqO+pRlAbRlvY8T -C1haGgSI/A1uZUKs/Zfnph0oEI0/hu1IIJ/SKBDtN5lvmZ/IzbOPIJWirlsllQIQ -7zvWbGd9c9+Rm3p04oTvhup99la7kZqevJK0QRdD/6NpCKsqP/0= +MIIDITCCAoqgAwIBAgIUVJTc+hOU+8Gk3JlqsX438Dk5c58wDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE3MTE0OVoXDTI5MDQyODE3MTE0OVowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUVJTc+hOU ++8Gk3JlqsX438Dk5c58wEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQAywJQaVNWJqW0R0T0xVOSoN1GLk9x9kKEuN67RN9CLin4dA97qa7Mr5W4P +FZ6vnh5CjOhQBRXV9xJUeYSdqVItNAUFK/fEzDdjf1nUfPlQ3OJ49u6CV01NoJ9m +usvY9kWcV46dqn2bk2MyfTTgvmeqP8fiMRPxxnVRkSzlldP5Fg== +-----END CERTIFICATE----- + +# certificate for us-east-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUE1y2NIKCU+Rg4uu4u32koG9QEYIwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE3MzQwMVoXDTI5MDQyODE3MzQwMVowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUE1y2NIKC +U+Rg4uu4u32koG9QEYIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQAlxSmwcWnhT4uAeSinJuz+1BTcKhVSWb5jT8pYjQb8ZoZkXXRGb09mvYeU +NeqOBr27rvRAnaQ/9LUQf72+SahDFuS4CMI8nwowytqbmwquqFr4dxA/SDADyRiF +ea1UoMuNHTY49J/1vPomqsVn7mugTp+TbjqCfOJTpu0temHcFA== +-----END CERTIFICATE----- + +# certificate for us-west-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUK2zmY9PUSTR7rc1k2OwPYu4+g7wwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE3MDI0M1oXDTI5MDQyODE3MDI0M1owXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUK2zmY9PU +STR7rc1k2OwPYu4+g7wwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQA1Ng4QmN4n7iPh5CnadSOc0ZfM7by0dBePwZJyGvOHdaw6P6E/vEk76KsC +Q8p+akuzVzVPkU4kBK/TRqLp19wEWoVwhhTaxHjQ1tTRHqXIVlrkw4JrtFbeNM21 +GlkSLonuzmNZdivn9WuQYeGe7nUD4w3q9GgiF3CPorJe+UxtbA== +-----END CERTIFICATE----- + +# certificate for us-west-2 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUFx8PxCkbHwpD31bOyCtyz3GclbgwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE3MjM1OVoXDTI5MDQyODE3MjM1OVowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUFx8PxCkb +HwpD31bOyCtyz3GclbgwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQBzOl+9Xy1+UsbUBI95HO9mbbdnuX+aMJXgG9uFZNjgNEbMcvx+h8P9IMko +z7PzFdheQQ1NLjsHH9mSR1SyC4m9ja6BsejH5nLBWyCdjfdP3muZM4O5+r7vUa1O +dWU+hP/T7DUrPAIVMOE7mpYa+WPWJrN6BlRwQkKQ7twm9kDalA== -----END CERTIFICATE----- # certificate for eu-south-1 @@ -93,7 +157,7 @@ NTpxxcXmUKquX+pHmIkK1LKDO8rNE84jqxrxRsfDi6by82fjVYf2pgjJW8R1FAw+ mL5WQRFexbfB5aXhcMo0AA== -----END CERTIFICATE----- -# certificate for cn-north-1, cn-northwest-1 +# certificate for cn-north-1 -----BEGIN CERTIFICATE----- MIIDCzCCAnSgAwIBAgIJALSOMbOoU2svMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0 @@ -114,6 +178,48 @@ oADS0ph+YUz5P/bUCm61wFjlxaTfwKcuTR3ytj7bFLoW5Bm7Sa+TCl3lOGb2taon SUDlRyNy1jJFstEZjOhs -----END CERTIFICATE----- +# certificate for cn-northwest-1 +-----BEGIN CERTIFICATE----- +MIIDCzCCAnSgAwIBAgIJALSOMbOoU2svMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV +BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0 +dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0yMzA3MDQw +ODM1MzlaFw0yODA3MDIwODM1MzlaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBX +YXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6 +b24gV2ViIFNlcnZpY2VzIExMQzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA +uhhUNlqAZdcWWB/OSDVDGk3OA99EFzOn/mJlmciQ/Xwu2dFJWmSCqEAE6gjufCjQ +q3voxAhC2CF+elKtJW/C0Sz/LYo60PUqd6iXF4h+upB9HkOOGuWHXsHBTsvgkgGA +1CGgel4U0Cdq+23eANr8N8m28UzljjSnTlrYCHtzN4sCAwEAAaOB1DCB0TALBgNV +HQ8EBAMCB4AwHQYDVR0OBBYEFBkZu3wT27NnYgrfH+xJz4HJaNJoMIGOBgNVHSME +gYYwgYOAFBkZu3wT27NnYgrfH+xJz4HJaNJooWCkXjBcMQswCQYDVQQGEwJVUzEZ +MBcGA1UECBMQV2FzaGluZ3RvbiBTdGF0ZTEQMA4GA1UEBxMHU2VhdHRsZTEgMB4G +A1UEChMXQW1hem9uIFdlYiBTZXJ2aWNlcyBMTEOCCQC0jjGzqFNrLzASBgNVHRMB +Af8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUAA4GBAECji43p+oPkYqmzll7e8Hgb +oADS0ph+YUz5P/bUCm61wFjlxaTfwKcuTR3ytj7bFLoW5Bm7Sa+TCl3lOGb2taon +2h+9NirRK6JYk87LMNvbS40HGPFumJL2NzEsGUeK+MRiWu+Oh5/lJGii3qw4YByx +SUDlRyNy1jJFstEZjOhs +-----END CERTIFICATE----- + +# certificate for eu-central-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUFD5GsmkxRuecttwsCG763m3u63UwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE1NTUyOVoXDTI5MDQyODE1NTUyOVowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUFD5Gsmkx +RuecttwsCG763m3u63UwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQBBh0WaXlBsW56Hqk588MmJxsOrvcKfDjF57RgEDgnGnQaJcStCVWDO9UYO +JX2tdsPw+E7AjDqjsuxYaotLn3Mr3mK0sNOXq9BljBnWD4pARg89KZnZI8FN35HQ +O/LYOVHCknuPL123VmVRNs51qQA9hkPjvw21UzpDLxaUxt9Z/w== +-----END CERTIFICATE----- + # certificate for eu-central-2 -----BEGIN CERTIFICATE----- MIICMzCCAZygAwIBAgIGAXjSGFGiMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT @@ -130,6 +236,27 @@ NBElvPCDKFvTJl4QQhToy056llO5GvdS9RK+H8xrP2mrqngApoKTApv93vHBixgF Sn5KrczRO0YSm3OjkqbydU7DFlmkXXR7GYE+5jbHvQHYiT1J5sMu -----END CERTIFICATE----- +# certificate for ap-south-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUDLA+x6tTAP3LRTr0z6nOxfsozdMwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE0MTMwMVoXDTI5MDQyODE0MTMwMVowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUDLA+x6tT +AP3LRTr0z6nOxfsozdMwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQAZ7rYKoAwwiiH1M5GJbrT/BEk3OO2VrEPw8ZxgpqQ/EKlzMlOs/0Cyrmp7 +UYyUgYFQe5nq37Z94rOUSeMgv/WRxaMwrLlLqD78cuF9DSkXaZIX/kECtVaUnjk8 +BZx0QhoIHOpQocJUSlm/dLeMuE0+0A3HNR6JVktGsUdv9ulmKw== +-----END CERTIFICATE----- + # certificate for ap-south-2 -----BEGIN CERTIFICATE----- MIICMzCCAZygAwIBAgIGAXjwLj9CMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT @@ -146,6 +273,48 @@ ETwUZ9mTq2vxlV0KvuetCDNS5u4cJsxe/TGGbYP0yP2qfMl0cCImzRI5W0gn8gog dervfeT7nH5ih0TWEy/QDWfkQ601L4erm4yh4YQq8vcqAPSkf04N -----END CERTIFICATE----- +# certificate for ap-southeast-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUSqP6ih+++5KF07NXngrWf26mhSUwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE0MzAxNFoXDTI5MDQyODE0MzAxNFowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUSqP6ih++ ++5KF07NXngrWf26mhSUwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQAw13BxW11U/JL58j//Fmk7qqtrZTqXmaz1qm2WlIpJpW750MOcP4ux1uPy +eM0RdVZ4jHSMv5gtLAv/PjExBfw9n6vNCk+5GZG4Xec5DoapBZHXmfMo93sjxBFP +4x9rWn0GuwAVO9ukjYPevq2Rerilrq5VvppHtbATVNY2qecXDA== +-----END CERTIFICATE----- + +# certificate for ap-southeast-2 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUFxWyAdk4oiXIOC9PxcgjYYh71mwwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE1MjE0M1oXDTI5MDQyODE1MjE0M1owXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUFxWyAdk4 +oiXIOC9PxcgjYYh71mwwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQByjeQe6lr7fiIhoGdjBXYzDfkX0lGGvMIhRh57G1bbceQfaYdZd7Ptc0jl +bpycKGaTvhUdkpMOiV2Hi9dOOYawkdhyJDstmDNKu6P9+b6Kak8He5z3NU1tUR2Y +uTwcz7Ye8Nldx//ws3raErfTI7D6s9m63OX8cAJ/f8bNgikwpw== +-----END CERTIFICATE----- + # certificate for ap-southeast-3 -----BEGIN CERTIFICATE----- MIICMzCCAZygAwIBAgIGAXbVDG2yMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT @@ -226,25 +395,46 @@ WX00FTEj4hRVjameE1nENoO8Z7fUVloAFDlDo69fhkJeSvn51D1WRrPnoWGgEfr1 +OfK1bAcKTtfkkkP9r4RdwSjKzO5Zu/B+Wqm3kVEz/QNcz6npmA6 -----END CERTIFICATE----- -# certificate for us-gov-east-1 and us-gov-west-1 +# certificate for us-gov-east-1 -----BEGIN CERTIFICATE----- -MIIDCzCCAnSgAwIBAgIJAIe9Hnq82O7UMA0GCSqGSIb3DQEBCwUAMFwxCzAJBgNV -BAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0 -dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0yMTA3MTQx -NDI3NTdaFw0yNDA3MTMxNDI3NTdaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBX -YXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6 -b24gV2ViIFNlcnZpY2VzIExMQzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA -qaIcGFFTx/SO1W5G91jHvyQdGP25n1Y91aXCuOOWAUTvSvNGpXrI4AXNrQF+CmIO -C4beBASnHCx082jYudWBBl9Wiza0psYc9flrczSzVLMmN8w/c78F/95NfiQdnUQP -pvgqcMeJo82cgHkLR7XoFWgMrZJqrcUK0gnsQcb6kakCAwEAAaOB1DCB0TALBgNV -HQ8EBAMCB4AwHQYDVR0OBBYEFNWV53gWJz72F5B1ZVY4O/dfFYBPMIGOBgNVHSME -gYYwgYOAFNWV53gWJz72F5B1ZVY4O/dfFYBPoWCkXjBcMQswCQYDVQQGEwJVUzEZ -MBcGA1UECBMQV2FzaGluZ3RvbiBTdGF0ZTEQMA4GA1UEBxMHU2VhdHRsZTEgMB4G -A1UEChMXQW1hem9uIFdlYiBTZXJ2aWNlcyBMTEOCCQCHvR56vNju1DASBgNVHRMB -Af8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUAA4GBACrKjWj460GUPZCGm3/z0dIz -M2BPuH769wcOsqfFZcMKEysSFK91tVtUb1soFwH4/Lb/T0PqNrvtEwD1Nva5k0h2 -xZhNNRmDuhOhW1K9wCcnHGRBwY5t4lYL6hNV6hcrqYwGMjTjcAjBG2yMgznSNFle -Rwi/S3BFXISixNx9cILu +MIIDITCCAoqgAwIBAgIULVyrqjjwZ461qelPCiShB1KCCj4wDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDUwNzE1MjIzNloXDTI5MDUwNjE1MjIzNlowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCpohwYUVPH9I7Vbkb3WMe/JB0Y/bmfVj3VpcK445YBRO9K80al +esjgBc2tAX4KYg4Lht4EBKccLHTzaNi51YEGX1aLNrSmxhz1+WtzNLNUsyY3zD9z +vwX/3k1+JB2dRA+m+Cpwx4mjzZyAeQtHtegVaAytkmqtxQrSCexBxvqRqQIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU1ZXneBYnPvYXkHVlVjg7918V +gE8wgZkGA1UdIwSBkTCBjoAU1ZXneBYnPvYXkHVlVjg7918VgE+hYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IULVyrqjjw +Z461qelPCiShB1KCCj4wEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQBfAL/YZv0y3zmVbXjyxQCsDloeDCJjFKIu3ameEckeIWJbST9LMto0zViZ +puIAf05x6GQiEqfBMk+YMxJfcTmJB4Ebaj4egFlslJPSHyC2xuydHlr3B04INOH5 +Z2oCM68u6GGbj0jZjg7GJonkReG9N72kDva/ukwZKgq8zErQVQ== +-----END CERTIFICATE----- + +# certificate for us-gov-west-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUe5wGF3jfb7lUHzvDxmM/ktGCLwwwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDUwNzE3MzAzMloXDTI5MDUwNjE3MzAzMlowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCpohwYUVPH9I7Vbkb3WMe/JB0Y/bmfVj3VpcK445YBRO9K80al +esjgBc2tAX4KYg4Lht4EBKccLHTzaNi51YEGX1aLNrSmxhz1+WtzNLNUsyY3zD9z +vwX/3k1+JB2dRA+m+Cpwx4mjzZyAeQtHtegVaAytkmqtxQrSCexBxvqRqQIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQU1ZXneBYnPvYXkHVlVjg7918V +gE8wgZkGA1UdIwSBkTCBjoAU1ZXneBYnPvYXkHVlVjg7918VgE+hYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUe5wGF3jf +b7lUHzvDxmM/ktGCLwwwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQCbTdpx1Iob9SwUReY4exMnlwQlmkTLyA8tYGWzchCJOJJEPfsW0ryy1A0H +YIuvyUty3rJdp9ib8h3GZR71BkZnNddHhy06kPs4p8ewF8+d8OWtOJQcI+ZnFfG4 +KyM4rUsBrljpG2aOCm12iACEyrvgJJrS8VZwUDZS6mZEnn/lhA== -----END CERTIFICATE----- # certificate for ca-west-1 @@ -261,4 +451,188 @@ RZWaBDBJy9x8C2hW+w9lMQjFHkJ7Jy/PHCJ69EzebQIDAQABMA0GCSqGSIb3DQEB BQUAA4GBAGe9Snkz1A6rHBH6/5kDtYvtPYwhx2sXNxztbhkXErFk40Nw5l459NZx EeudxJBLoCkkSgYjhRcOZ/gvDVtWG7qyb6fAqgoisyAbk8K9LzxSim2S1nmT9vD8 4B/t/VvwQBylc+ej8kRxMH7fquZLp7IXfmtBzyUqu6Dpbne+chG2 ------END CERTIFICATE----- \ No newline at end of file +-----END CERTIFICATE----- + +# certificate for ap-northeast-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIULgwDh7TiDrPPBJwscqDwiBHkEFQwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTEyMjMxMFoXDTI5MDQyODEyMjMxMFowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IULgwDh7Ti +DrPPBJwscqDwiBHkEFQwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQBtjAglBde1t4F9EHCZOj4qnY6Gigy07Ou54i+lR77MhbpzE8V28Li9l+YT +QMIn6SzJqU3/fIycIro1OVY1lHmaKYgPGSEZxBenSBHfzwDLRmC9oRp4QMe0BjOC +gepj1lUoiN7OA6PtA+ycNlsP0oJvdBjhvayLiuM3tUfLTrgHbw== +-----END CERTIFICATE----- + +# certificate for ap-northeast-2 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUbBSn2UIO6vYk4iNWV0RPxJJtHlgwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTEzMzg0NloXDTI5MDQyODEzMzg0NlowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUbBSn2UIO +6vYk4iNWV0RPxJJtHlgwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQAmjTjalG8MGLqWTC2uYqEM8nzI3px1eo0ArvFRsyqQ3fgmWcQpxExqUqRy +l3+2134Kv8dFab04Gut5wlfRtc2OwPKKicmv/IXGN+9bKFnQFjTqif08NIzrDZch +aFT/uvxrIiM+oN2YsHq66GUhO2+xVRXDXVxM/VObFgPERbJpyA== +-----END CERTIFICATE----- + +# certificate for ap-northeast-3 +-----BEGIN CERTIFICATE----- +MIICMzCCAZygAwIBAgIGAYPou9weMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNVBAYT +AlVTMRkwFwYDVQQIDBBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHDAdTZWF0dGxl +MSAwHgYDVQQKDBdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAgFw0yMjEwMTgwMTM2 +MDlaGA8yMjAxMTAxODAxMzYwOVowXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgMEFdh +c2hpbmd0b24gU3RhdGUxEDAOBgNVBAcMB1NlYXR0bGUxIDAeBgNVBAoMF0FtYXpv +biBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK +1kIcG5Q6adBXQM75GldfTSiXl7tn54p10TnspI0ErDdb2B6q2Ji/v4XBVH13ZCMg +qlRHMqV8AWI5iO6gFn2A9sN3AZXTMqwtZeiDdebq3k6Wt7ieYvpXTg0qvgsjQIov +RZWaBDBJy9x8C2hW+w9lMQjFHkJ7Jy/PHCJ69EzebQIDAQABMA0GCSqGSIb3DQEB +BQUAA4GBAGe9Snkz1A6rHBH6/5kDtYvtPYwhx2sXNxztbhkXErFk40Nw5l459NZx +EeudxJBLoCkkSgYjhRcOZ/gvDVtWG7qyb6fAqgoisyAbk8K9LzxSim2S1nmT9vD8 +4B/t/VvwQBylc+ej8kRxMH7fquZLp7IXfmtBzyUqu6Dpbne+chG2 +-----END CERTIFICATE----- + +# certificate for ca-central-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUIrLgixJJB5C4G8z6pZ5rB0JU2aQwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE1MzU0M1oXDTI5MDQyODE1MzU0M1owXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUIrLgixJJ +B5C4G8z6pZ5rB0JU2aQwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQBHiQJmzyFAaSYs8SpiRijIDZW2RIo7qBKb/pI3rqK6yOWDlPuMr6yNI81D +IrKGGftg4Z+2KETYU4x76HSf0s//vfH3QA57qFaAwddhKYy4BhteFQl/Wex3xTlX +LiwI07kwJvJy3mS6UfQ4HcvZy219tY+0iyOWrz/jVxwq7TOkCw== +-----END CERTIFICATE----- + +# certificate for eu-west-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUakDaQ1Zqy87Hy9ESXA1pFC116HkwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE2MTgxMFoXDTI5MDQyODE2MTgxMFowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUakDaQ1Zq +y87Hy9ESXA1pFC116HkwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQADIkn/MqaLGPuK5+prZZ5Ox4bBZLPtreO2C7r0pqU2kPM2lVPyYYydkvP0 +lgSmmsErGu/oL9JNztDe2oCA+kNy17ehcsf8cw0uP861czNFKCeU8b7FgBbL+sIm +qi33rAq6owWGi/5uEcfCR+JP7W+oSYVir5r/yDmWzx+BVH5S/g== +-----END CERTIFICATE----- + +# certificate for eu-west-2 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUCgCV/DPxYNND/swDgEKGiC5I+EwwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE2MjkxNFoXDTI5MDQyODE2MjkxNFowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUCgCV/DPx +YNND/swDgEKGiC5I+EwwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQATPu/sOE2esNa4+XPEGKlEJSgqzyBSQLQc+VWo6FAJhGG9fp7D97jhHeLC +5vwfmtTAfnGBxadfAOT3ASkxnOZhXtnRna460LtnNHm7ArCVgXKJo7uBn6ViXtFh +uEEw4y6p9YaLQna+VC8Xtgw6WKq2JXuKzuhuNKSFaGGw9vRcHg== +-----END CERTIFICATE----- + +# certificate for eu-west-3 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUaC9fX57UDr6u1vBvsCsECKBZQyIwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE2MzczOFoXDTI5MDQyODE2MzczOFowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUaC9fX57U +Dr6u1vBvsCsECKBZQyIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQCARv1bQEDaMEzYI0nPlu8GHcMXgmgA94HyrXhMMcaIlQwocGBs6VILGVhM +TXP2r3JFaPEpmXSQNQHvGA13clKwAZbni8wtzv6qXb4L4muF34iQRHF0nYrEDoK7 +mMPR8+oXKKuPO/mv/XKo6XAV5DDERdSYHX5kkA2R9wtvyZjPnQ== +-----END CERTIFICATE----- + +# certificate for eu-north-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUN1c9U6U/xiVDFgJcYKZB4NkH1QEwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE2MDYwM1oXDTI5MDQyODE2MDYwM1owXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUN1c9U6U/ +xiVDFgJcYKZB4NkH1QEwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQBTIQdoFSDRHkpqNPUbZ9WXR2O5v/9bpmHojMYZb3Hw46wsaRso7STiGGX/ +tRqjIkPUIXsdhZ3+7S/RmhFznmZc8e0bjU4n5vi9CJtQSt+1u4E17+V2bF+D3h/7 +wcfE0l3414Q8JaTDtfEf/aF3F0uyBvr4MDMd7mFvAMmDmBPSlA== +-----END CERTIFICATE----- + +# certificate for sa-east-1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIUX4Bh4MQ86Roh37VDRRX1MNOB3TcwDQYJKoZIhvcNAQEL +BQAwXDELMAkGA1UEBhMCVVMxGTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAO +BgNVBAcTB1NlYXR0bGUxIDAeBgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExD +MB4XDTI0MDQyOTE2NDYwOVoXDTI5MDQyODE2NDYwOVowXDELMAkGA1UEBhMCVVMx +GTAXBgNVBAgTEFdhc2hpbmd0b24gU3RhdGUxEDAOBgNVBAcTB1NlYXR0bGUxIDAe +BgNVBAoTF0FtYXpvbiBXZWIgU2VydmljZXMgTExDMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCHvRjf/0kStpJ248khtIaN8qkDN3tkw4VjvA9nvPl2anJO+eIB +UqPfQG09kZlwpWpmyO8bGB2RWqWxCwuB/dcnIob6w420k9WY5C0IIGtDRNauN3ku +vGXkw3HEnF0EjYr0pcyWUvByWY4KswZV42X7Y7XSS13hOIcL6NLA+H94/QIDAQAB +o4HfMIHcMAsGA1UdDwQEAwIHgDAdBgNVHQ4EFgQUJdbMCBXKtvCcWdwUUizvtUF2 +UTgwgZkGA1UdIwSBkTCBjoAUJdbMCBXKtvCcWdwUUizvtUF2UTihYKReMFwxCzAJ +BgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdT +ZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQ4IUX4Bh4MQ8 +6Roh37VDRRX1MNOB3TcwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsF +AAOBgQBnhocfH6ZIX6F5K9+Y9V4HFk8vSaaKL5ytw/P5td1h9ej94KF3xkZ5fyjN +URvGQv3kNmNJBoNarcP9I7JIMjsNPmVzqWawyCEGCZImoARxSS3Fc5EAs2PyBfcD +9nCtzMTaKO09Xyq0wqXVYn1xJsE5d5yBDsGrzaTHKjxo61+ezQ== +-----END CERTIFICATE----- diff --git a/authority/provisioner/aws_test.go b/authority/provisioner/aws_test.go index f2485e93..5af0ec67 100644 --- a/authority/provisioner/aws_test.go +++ b/authority/provisioner/aws_test.go @@ -896,5 +896,5 @@ func TestAWS_HardcodedCertificates(t *testing.T) { assert.True(t, cert.NotAfter.After(time.Now())) certs = append(certs, cert) } - assert.Len(t, 15, certs, "expected 15 certificates in aws_certificates.pem") + assert.Len(t, 33, certs, "expected 33 certificates in aws_certificates.pem, but got %d", len(certs)) } diff --git a/authority/provisioner/claims.go b/authority/provisioner/claims.go index dcf679b3..993bf498 100644 --- a/authority/provisioner/claims.go +++ b/authority/provisioner/claims.go @@ -234,24 +234,24 @@ func (c *Claimer) IsSSHCAEnabled() bool { // Validate validates and modifies the Claims with default values. func (c *Claimer) Validate() error { var ( - min = c.MinTLSCertDuration() - max = c.MaxTLSCertDuration() - def = c.DefaultTLSCertDuration() + minDur = c.MinTLSCertDuration() + maxDur = c.MaxTLSCertDuration() + defDur = c.DefaultTLSCertDuration() ) switch { - case min <= 0: + case minDur <= 0: return errors.Errorf("claims: MinTLSCertDuration must be greater than 0") - case max <= 0: + case maxDur <= 0: return errors.Errorf("claims: MaxTLSCertDuration must be greater than 0") - case def <= 0: + case defDur <= 0: return errors.Errorf("claims: DefaultTLSCertDuration must be greater than 0") - case max < min: + case maxDur < minDur: return errors.Errorf("claims: MaxCertDuration cannot be less "+ - "than MinCertDuration: MaxCertDuration - %v, MinCertDuration - %v", max, min) - case def < min: - return errors.Errorf("claims: DefaultCertDuration cannot be less than MinCertDuration: DefaultCertDuration - %v, MinCertDuration - %v", def, min) - case max < def: - return errors.Errorf("claims: MaxCertDuration cannot be less than DefaultCertDuration: MaxCertDuration - %v, DefaultCertDuration - %v", max, def) + "than MinCertDuration: MaxCertDuration - %v, MinCertDuration - %v", maxDur, minDur) + case defDur < minDur: + return errors.Errorf("claims: DefaultCertDuration cannot be less than MinCertDuration: DefaultCertDuration - %v, MinCertDuration - %v", defDur, minDur) + case maxDur < defDur: + return errors.Errorf("claims: MaxCertDuration cannot be less than DefaultCertDuration: MaxCertDuration - %v, DefaultCertDuration - %v", maxDur, defDur) default: return nil } diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 2296b1b0..8f6211d3 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -30,6 +30,12 @@ const gcpCertsURL = "https://www.googleapis.com/oauth2/v3/certs" // gcpIdentityURL is the base url for the identity document in GCP. const gcpIdentityURL = "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity" +// DefaultDisableSSHCAHost is the default value for SSH Host CA used when DisableSSHCAHost is not set +var DefaultDisableSSHCAHost = false + +// DefaultDisableSSHCAUser is the default value for SSH User CA used when DisableSSHCAUser is not set +var DefaultDisableSSHCAUser = true + // gcpPayload extends jwt.Claims with custom GCP attributes. type gcpPayload struct { jose.Claims @@ -89,6 +95,8 @@ type GCP struct { ProjectIDs []string `json:"projectIDs"` DisableCustomSANs bool `json:"disableCustomSANs"` DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` + DisableSSHCAUser *bool `json:"disableSSHCAUser,omitempty"` + DisableSSHCAHost *bool `json:"disableSSHCAHost,omitempty"` InstanceAge Duration `json:"instanceAge,omitempty"` Claims *Claims `json:"claims,omitempty"` Options *Options `json:"options,omitempty"` @@ -199,6 +207,14 @@ func (p *GCP) GetIdentityToken(subject, caURL string) (string, error) { // Init validates and initializes the GCP provisioner. func (p *GCP) Init(config Config) (err error) { + if p.DisableSSHCAHost == nil { + p.DisableSSHCAHost = &DefaultDisableSSHCAHost + } + + if p.DisableSSHCAUser == nil { + p.DisableSSHCAUser = &DefaultDisableSSHCAUser + } + switch { case p.Type == "": return errors.New("provisioner type cannot be empty") @@ -387,31 +403,41 @@ func (p *GCP) authorizeToken(token string) (*gcpPayload, error) { } // AuthorizeSSHSign returns the list of SignOption for a SignSSH request. -func (p *GCP) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, error) { - if !p.ctl.Claimer.IsSSHCAEnabled() { - return nil, errs.Unauthorized("gcp.AuthorizeSSHSign; sshCA is disabled for gcp provisioner '%s'", p.GetName()) +func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { + certType, hasCertType := CertTypeFromContext(ctx) + if !hasCertType { + certType = SSHHostCert } + + err := p.isUnauthorizedToIssueSSHCert(certType) + if err != nil { + return nil, err + } + claims, err := p.authorizeToken(token) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSSHSign") } - ce := claims.Google.ComputeEngine + var principals []string + var keyID string + var defaults SignSSHOptions + var ct sshutil.CertType + var template string + + switch certType { + case SSHHostCert: + defaults, keyID, principals, ct, template = p.genHostOptions(ctx, claims) + case SSHUserCert: + defaults, keyID, principals, ct, template = p.genUserOptions(ctx, claims) + default: + return nil, errs.Unauthorized("gcp.AuthorizeSSHSign; invalid requested certType") + } + signOptions := []SignOption{} - // Enforce host certificate. - defaults := SignSSHOptions{ - CertType: SSHHostCert, - } - - // Validated principals. - principals := []string{ - fmt.Sprintf("%s.c.%s.internal", ce.InstanceName, ce.ProjectID), - fmt.Sprintf("%s.%s.c.%s.internal", ce.InstanceName, ce.Zone, ce.ProjectID), - } - - // Only enforce known principals if disable custom sans is true. - if p.DisableCustomSANs { + // Only enforce known principals if disable custom sans is true, or it is a user cert request + if p.DisableCustomSANs || certType == SSHUserCert { defaults.Principals = principals } else { // Check that at least one principal is sent in the request. @@ -421,12 +447,12 @@ func (p *GCP) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e } // Certificate templates. - data := sshutil.CreateTemplateData(sshutil.HostCert, ce.InstanceName, principals) + data := sshutil.CreateTemplateData(ct, keyID, principals) if v, err := unsafeParseSigned(token); err == nil { data.SetToken(v) } - templateOptions, err := CustomSSHTemplateOptions(p.Options, data, sshutil.DefaultIIDTemplate) + templateOptions, err := CustomSSHTemplateOptions(p.Options, data, template) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "gcp.AuthorizeSSHSign") } @@ -445,12 +471,54 @@ func (p *GCP) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e // Require all the fields in the SSH certificate &sshCertDefaultValidator{}, // Ensure that all principal names are allowed - newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), nil), + newSSHNamePolicyValidator(p.ctl.getPolicy().getSSHHost(), p.ctl.getPolicy().getSSHUser()), // Call webhooks p.ctl.newWebhookController( data, linkedca.Webhook_SSH, - webhook.WithAuthorizationPrincipal(ce.InstanceID), + webhook.WithAuthorizationPrincipal(keyID), ), ), nil } + +func (p *GCP) genHostOptions(_ context.Context, claims *gcpPayload) (SignSSHOptions, string, []string, sshutil.CertType, string) { + ce := claims.Google.ComputeEngine + keyID := ce.InstanceName + + principals := []string{ + fmt.Sprintf("%s.c.%s.internal", ce.InstanceName, ce.ProjectID), + fmt.Sprintf("%s.%s.c.%s.internal", ce.InstanceName, ce.Zone, ce.ProjectID), + } + + return SignSSHOptions{CertType: SSHHostCert}, keyID, principals, sshutil.HostCert, sshutil.DefaultIIDTemplate +} + +func FormatServiceAccountUsername(serviceAccountId string) string { + return fmt.Sprintf("sa_%v", serviceAccountId) +} + +func (p *GCP) genUserOptions(_ context.Context, claims *gcpPayload) (SignSSHOptions, string, []string, sshutil.CertType, string) { + keyID := claims.Email + principals := []string{ + FormatServiceAccountUsername(claims.Subject), + claims.Email, + } + + return SignSSHOptions{CertType: SSHUserCert}, keyID, principals, sshutil.UserCert, sshutil.DefaultTemplate +} + +func (p *GCP) isUnauthorizedToIssueSSHCert(certType string) error { + if !p.ctl.Claimer.IsSSHCAEnabled() { + return errs.Unauthorized("gcp.AuthorizeSSHSign; sshCA is disabled for gcp provisioner '%s'", p.GetName()) + } + + if certType == SSHHostCert && *p.DisableSSHCAHost { + return errs.Unauthorized("gcp.AuthorizeSSHSign; sshCA for Hosts is disabled for gcp provisioner '%s'", p.GetName()) + } + + if certType == SSHUserCert && *p.DisableSSHCAUser { + return errs.Unauthorized("gcp.AuthorizeSSHSign; sshCA for Users is disabled for gcp provisioner '%s'", p.GetName()) + } + + return nil +} diff --git a/authority/provisioner/gcp_test.go b/authority/provisioner/gcp_test.go index ef791614..5f5587a4 100644 --- a/authority/provisioner/gcp_test.go +++ b/authority/provisioner/gcp_test.go @@ -186,6 +186,7 @@ func TestGCP_Init(t *testing.T) { args args wantErr bool }{ + {"ok", fields{"GCP", "name", nil, zero, nil}, args{config, srv.URL}, false}, {"ok", fields{"GCP", "name", nil, zero, nil}, args{config, srv.URL}, false}, {"ok", fields{"GCP", "name", []string{"service-account"}, zero, nil}, args{config, srv.URL}, false}, {"ok", fields{"GCP", "name", []string{"service-account"}, Duration{Duration: 1 * time.Minute}, nil}, args{config, srv.URL}, false}, @@ -211,6 +212,14 @@ func TestGCP_Init(t *testing.T) { if err := p.Init(tt.args.config); (err != nil) != tt.wantErr { t.Errorf("GCP.Init() error = %v, wantErr %v", err, tt.wantErr) } + + if *p.DisableSSHCAUser != true { + t.Errorf("By default DisableSSHCAUser should be true") + } + + if *p.DisableSSHCAHost != false { + t.Errorf("By default DisableSSHCAHost should be false") + } }) } } @@ -592,6 +601,9 @@ func TestGCP_AuthorizeSSHSign(t *testing.T) { p1, err := generateGCP() assert.FatalError(t, err) p1.DisableCustomSANs = true + // enable ssh user CA + disableSSCAUser := false + p1.DisableSSHCAUser = &disableSSCAUser p2, err := generateGCP() assert.FatalError(t, err) @@ -605,6 +617,12 @@ func TestGCP_AuthorizeSSHSign(t *testing.T) { p3.ctl.Claimer, err = NewClaimer(p3.Claims, globalProvisionerClaims) assert.FatalError(t, err) + p4, err := generateGCP() + assert.FatalError(t, err) + // disable ssh host CA + disableSSCAHost := true + p4.DisableSSHCAHost = &disableSSCAHost + t1, err := generateGCPToken(p1.ServiceAccounts[0], "https://accounts.google.com", p1.GetID(), "instance-id", "instance-name", "project-id", "zone", @@ -647,6 +665,10 @@ func TestGCP_AuthorizeSSHSign(t *testing.T) { CertType: "host", Principals: []string{"foo.bar", "bar.foo"}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(hostDuration)), } + expectedUserOptions := &SignSSHOptions{ + CertType: "user", Principals: []string{FormatServiceAccountUsername(p1.ServiceAccounts[0]), "foo@developer.gserviceaccount.com"}, + ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(p1.ctl.Claimer.DefaultUserSSHCertDuration())), + } type args struct { token string @@ -664,22 +686,29 @@ func TestGCP_AuthorizeSSHSign(t *testing.T) { }{ {"ok", p1, args{t1, SignSSHOptions{}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"ok-rsa2048", p1, args{t1, SignSSHOptions{}, rsa2048.Public()}, expectedHostOptions, http.StatusOK, false, false}, - {"ok-type", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-type-host", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ok-type-user", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, expectedUserOptions, http.StatusOK, false, false}, {"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"ok-principal1", p1, args{t1, SignSSHOptions{Principals: []string{"instance-name.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal1, http.StatusOK, false, false}, {"ok-principal2", p1, args{t1, SignSSHOptions{Principals: []string{"instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptionsPrincipal2, http.StatusOK, false, false}, {"ok-options", p1, args{t1, SignSSHOptions{CertType: "host", Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"ok-custom", p2, args{t2, SignSSHOptions{Principals: []string{"foo.bar", "bar.foo"}}, pub}, expectedCustomOptions, http.StatusOK, false, false}, {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedHostOptions, http.StatusOK, false, true}, - {"fail-type", p1, args{t1, SignSSHOptions{CertType: "user"}, pub}, nil, http.StatusOK, false, true}, {"fail-principal", p1, args{t1, SignSSHOptions{Principals: []string{"smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, {"fail-extra-principal", p1, args{t1, SignSSHOptions{Principals: []string{"instance-name.c.project-id.internal", "instance-name.zone.c.project-id.internal", "smallstep.com"}}, pub}, nil, http.StatusOK, false, true}, {"fail-sshCA-disabled", p3, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, + {"fail-type-host", p4, args{"foo", SignSSHOptions{CertType: "host"}, pub}, nil, http.StatusUnauthorized, true, false}, + {"fail-type-user", p4, args{"foo", SignSSHOptions{CertType: "host"}, pub}, nil, http.StatusUnauthorized, true, false}, {"fail-invalid-token", p1, args{"foo", SignSSHOptions{}, pub}, expectedHostOptions, http.StatusUnauthorized, true, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := tt.gcp.AuthorizeSSHSign(context.Background(), tt.args.token) + ctx := context.Background() + if tt.args.sshOpts.CertType == SSHUserCert { + ctx = NewContextWithCertType(ctx, SSHUserCert) + } + + got, err := tt.gcp.AuthorizeSSHSign(ctx, tt.args.token) if (err != nil) != tt.wantErr { t.Errorf("GCP.AuthorizeSSHSign() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/authority/provisioner/method.go b/authority/provisioner/method.go index 19aa6224..d01ce12c 100644 --- a/authority/provisioner/method.go +++ b/authority/provisioner/method.go @@ -78,3 +78,17 @@ func TokenFromContext(ctx context.Context) (string, bool) { token, ok := ctx.Value(tokenKey{}).(string) return token, ok } + +// The key to save the certTypeKey in the context. +type certTypeKey struct{} + +// NewContextWithCertType creates a new context with the given CertType. +func NewContextWithCertType(ctx context.Context, certType string) context.Context { + return context.WithValue(ctx, certTypeKey{}, certType) +} + +// CertTypeFromContext returns the certType stored in the given context. +func CertTypeFromContext(ctx context.Context) (string, bool) { + certType, ok := ctx.Value(certTypeKey{}).(string) + return certType, ok +} diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 06823e2f..69b5be76 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -93,6 +93,8 @@ type OIDC struct { ListenAddress string `json:"listenAddress,omitempty"` Claims *Claims `json:"claims,omitempty"` Options *Options `json:"options,omitempty"` + Scopes []string `json:"scopes,omitempty"` + AuthParams []string `json:"authParams,omitempty"` configuration openIDConfiguration keyStore *keyStore ctl *Controller diff --git a/authority/provisioner/options_test.go b/authority/provisioner/options_test.go index 405ec8b7..3abb927e 100644 --- a/authority/provisioner/options_test.go +++ b/authority/provisioner/options_test.go @@ -189,7 +189,7 @@ func TestTemplateOptions(t *testing.T) { func TestCustomTemplateOptions(t *testing.T) { csr := parseCertificateRequest(t, "testdata/certs/ecdsa.csr") - csrCertificate := `{"version":0,"subject":{"commonName":"foo"},"dnsNames":["foo"],"emailAddresses":null,"ipAddresses":null,"uris":null,"sans":null,"extensions":[{"id":"2.5.29.17","critical":false,"value":"MAWCA2Zvbw=="}],"signatureAlgorithm":""}` + csrCertificate := `{"version":0,"subject":{"commonName":"foo"},"rawSubject":"MA4xDDAKBgNVBAMTA2Zvbw==","dnsNames":["foo"],"emailAddresses":null,"ipAddresses":null,"uris":null,"sans":null,"extensions":[{"id":"2.5.29.17","critical":false,"value":"MAWCA2Zvbw=="}],"signatureAlgorithm":""}` data := x509util.TemplateData{ x509util.SubjectKey: x509util.Subject{ CommonName: "foobar", diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index a9b17066..94a3285b 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/pkg/errors" + kmsapi "go.step.sm/crypto/kms/apiv1" "golang.org/x/crypto/ssh" "github.com/smallstep/certificates/errs" @@ -33,6 +34,31 @@ type Interface interface { AuthorizeSSHRekey(ctx context.Context, token string) (*ssh.Certificate, []SignOption, error) } +// Uninitialized represents a disabled provisioner. Uninitialized provisioners +// are created when the Init methods fails. +type Uninitialized struct { + Interface + Reason error +} + +// MarshalJSON returns the JSON encoding of the provisioner with the disabled +// reason. +func (p Uninitialized) MarshalJSON() ([]byte, error) { + provisionerJSON, err := json.Marshal(p.Interface) + if err != nil { + return nil, err + } + reasonJSON, err := json.Marshal(struct { + State string `json:"state"` + StateReason string `json:"stateReason"` + }{"Uninitialized", p.Reason.Error()}) + if err != nil { + return nil, err + } + reasonJSON[0] = ',' + return append(provisionerJSON[:len(provisionerJSON)-1], reasonJSON...), nil +} + // ErrAllowTokenReuse is an error that is returned by provisioners that allows // the reuse of tokens. // @@ -206,6 +232,13 @@ type SSHKeys struct { HostKeys []ssh.PublicKey } +// SCEPKeyManager is a KMS interface that combines a KeyManager with a +// Decrypter. +type SCEPKeyManager interface { + kmsapi.KeyManager + kmsapi.Decrypter +} + // Config defines the default parameters used in the initialization of // provisioners. type Config struct { @@ -226,6 +259,8 @@ type Config struct { AuthorizeSSHRenewFunc AuthorizeSSHRenewFunc // WebhookClient is an http client to use in webhook request WebhookClient *http.Client + // SCEPKeyManager, if defined, is the interface used by SCEP provisioners. + SCEPKeyManager SCEPKeyManager } type provisioner struct { @@ -320,7 +355,7 @@ func (b *base) AuthorizeSSHSign(context.Context, string) ([]SignOption, error) { return nil, errs.Unauthorized("provisioner.AuthorizeSSHSign not implemented") } -// AuthorizeRevoke returns an unimplemented error. Provisioners should overwrite +// AuthorizeSSHRevoke returns an unimplemented error. Provisioners should overwrite // this method if they will support authorizing tokens for revoking SSH Certificates. func (b *base) AuthorizeSSHRevoke(context.Context, string) error { return errs.Unauthorized("provisioner.AuthorizeSSHRevoke not implemented") diff --git a/authority/provisioner/provisioner_test.go b/authority/provisioner/provisioner_test.go index 865e5291..880c53c5 100644 --- a/authority/provisioner/provisioner_test.go +++ b/authority/provisioner/provisioner_test.go @@ -6,10 +6,10 @@ import ( "net/http" "testing" - "golang.org/x/crypto/ssh" - - "github.com/smallstep/assert" + "github.com/go-jose/go-jose/v3" "github.com/smallstep/certificates/api/render" + "github.com/stretchr/testify/assert" + "golang.org/x/crypto/ssh" ) func TestType_String(t *testing.T) { @@ -149,11 +149,11 @@ func TestDefaultIdentityFunc(t *testing.T) { identity, err := DefaultIdentityFunc(context.Background(), tc.p, tc.email) if err != nil { if assert.NotNil(t, tc.err) { - assert.Equals(t, tc.err.Error(), err.Error()) + assert.Equal(t, tc.err.Error(), err.Error()) } } else { if assert.Nil(t, tc.err) { - assert.Equals(t, identity.Usernames, tc.identity.Usernames) + assert.Equal(t, identity.Usernames, tc.identity.Usernames) } } }) @@ -243,9 +243,43 @@ func TestUnimplementedMethods(t *testing.T) { } 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.Equal(t, http.StatusUnauthorized, sc.StatusCode()) } - assert.Equals(t, err.Error(), msg) + assert.Equal(t, msg, err.Error()) + }) + } +} + +func TestUninitialized_MarshalJSON(t *testing.T) { + p := &JWK{ + Name: "bad-provisioner", + Type: "JWK", + Key: &jose.JSONWebKey{ + Key: []byte("foo"), + }, + } + + type fields struct { + Interface Interface + Reason error + } + tests := []struct { + name string + fields fields + want []byte + assertion assert.ErrorAssertionFunc + }{ + {"ok", fields{p, errors.New("bad key")}, []byte(`{"type":"JWK","name":"bad-provisioner","key":{"kty":"oct","k":"Zm9v"},"state":"Uninitialized","stateReason":"bad key"}`), assert.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := Uninitialized{ + Interface: tt.fields.Interface, + Reason: tt.fields.Reason, + } + got, err := p.MarshalJSON() + tt.assertion(t, err) + assert.Equal(t, tt.want, got) }) } } diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index f4067bc5..7213285c 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -15,7 +15,6 @@ import ( "go.step.sm/crypto/kms" kmsapi "go.step.sm/crypto/kms/apiv1" - "go.step.sm/crypto/kms/uri" "go.step.sm/linkedca" "github.com/smallstep/certificates/webhook" @@ -59,7 +58,7 @@ type SCEP struct { encryptionAlgorithm int challengeValidationController *challengeValidationController notificationController *notificationController - keyManager kmsapi.KeyManager + keyManager SCEPKeyManager decrypter crypto.Decrypter decrypterCertificate *x509.Certificate signer crypto.Signer @@ -269,34 +268,38 @@ func (s *SCEP) Init(config Config) (err error) { ) // parse the decrypter key PEM contents if available - if decryptionKeyPEM := s.DecrypterKeyPEM; len(decryptionKeyPEM) > 0 { + if len(s.DecrypterKeyPEM) > 0 { // try reading the PEM for validation - block, rest := pem.Decode(decryptionKeyPEM) + block, rest := pem.Decode(s.DecrypterKeyPEM) if len(rest) > 0 { return errors.New("failed parsing decrypter key: trailing data") } if block == nil { return errors.New("failed parsing decrypter key: no PEM block found") } + opts := kms.Options{ Type: kmsapi.SoftKMS, } - if s.keyManager, err = kms.New(context.Background(), opts); err != nil { + km, err := kms.New(context.Background(), opts) + if err != nil { return fmt.Errorf("failed initializing kms: %w", err) } - kmsDecrypter, ok := s.keyManager.(kmsapi.Decrypter) + scepKeyManager, ok := km.(SCEPKeyManager) if !ok { return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type) } - if s.decrypter, err = kmsDecrypter.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKeyPEM: decryptionKeyPEM, + s.keyManager = scepKeyManager + + if s.decrypter, err = s.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKeyPEM: s.DecrypterKeyPEM, Password: []byte(s.DecrypterKeyPassword), PasswordPrompter: kmsapi.NonInteractivePasswordPrompter, }); err != nil { return fmt.Errorf("failed creating decrypter: %w", err) } if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKeyPEM: decryptionKeyPEM, // TODO(hs): support distinct signer key in the future? + SigningKeyPEM: s.DecrypterKeyPEM, // TODO(hs): support distinct signer key in the future? Password: []byte(s.DecrypterKeyPassword), PasswordPrompter: kmsapi.NonInteractivePasswordPrompter, }); err != nil { @@ -304,41 +307,44 @@ func (s *SCEP) Init(config Config) (err error) { } } - if decryptionKeyURI := s.DecrypterKeyURI; decryptionKeyURI != "" { - u, err := uri.Parse(s.DecrypterKeyURI) + if s.DecrypterKeyURI != "" { + kmsType, err := kmsapi.TypeOf(s.DecrypterKeyURI) if err != nil { return fmt.Errorf("failed parsing decrypter key: %w", err) } - var kmsType kmsapi.Type - switch { - case u.Scheme != "": - kmsType = kms.Type(u.Scheme) - default: - kmsType = kmsapi.SoftKMS + + if config.SCEPKeyManager != nil { + s.keyManager = config.SCEPKeyManager + } else { + if kmsType == kmsapi.DefaultKMS { + kmsType = kmsapi.SoftKMS + } + opts := kms.Options{ + Type: kmsType, + URI: s.DecrypterKeyURI, + } + km, err := kms.New(context.Background(), opts) + if err != nil { + return fmt.Errorf("failed initializing kms: %w", err) + } + scepKeyManager, ok := km.(SCEPKeyManager) + if !ok { + return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type) + } + s.keyManager = scepKeyManager } - opts := kms.Options{ - Type: kmsType, - URI: s.DecrypterKeyURI, - } - if s.keyManager, err = kms.New(context.Background(), opts); err != nil { - return fmt.Errorf("failed initializing kms: %w", err) - } - kmsDecrypter, ok := s.keyManager.(kmsapi.Decrypter) - if !ok { - return fmt.Errorf("%q is not a kmsapi.Decrypter", opts.Type) - } - if kmsType != "softkms" { // TODO(hs): this should likely become more transparent? - decryptionKeyURI = u.Opaque - } - if s.decrypter, err = kmsDecrypter.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ - DecryptionKey: decryptionKeyURI, + + // Create decrypter and signer with the same key: + // TODO(hs): support distinct signer key in the future? + if s.decrypter, err = s.keyManager.CreateDecrypter(&kmsapi.CreateDecrypterRequest{ + DecryptionKey: s.DecrypterKeyURI, Password: []byte(s.DecrypterKeyPassword), PasswordPrompter: kmsapi.NonInteractivePasswordPrompter, }); err != nil { return fmt.Errorf("failed creating decrypter: %w", err) } if s.signer, err = s.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{ - SigningKey: decryptionKeyURI, // TODO(hs): support distinct signer key in the future? + SigningKey: s.DecrypterKeyURI, Password: []byte(s.DecrypterKeyPassword), PasswordPrompter: kmsapi.NonInteractivePasswordPrompter, }); err != nil { diff --git a/authority/provisioner/scep_test.go b/authority/provisioner/scep_test.go index 2e9f3419..4e2081ee 100644 --- a/authority/provisioner/scep_test.go +++ b/authority/provisioner/scep_test.go @@ -2,19 +2,27 @@ package provisioner import ( "context" + "crypto" + "crypto/rand" + "crypto/rsa" "crypto/x509" + "crypto/x509/pkix" "encoding/json" + "encoding/pem" "errors" "net/http" "net/http/httptest" + "os" + "path/filepath" "testing" + "github.com/smallstep/certificates/webhook" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - + "go.step.sm/crypto/kms/softkms" + "go.step.sm/crypto/minica" + "go.step.sm/crypto/pemutil" "go.step.sm/linkedca" - - "github.com/smallstep/certificates/webhook" ) func Test_challengeValidationController_Validate(t *testing.T) { @@ -366,3 +374,270 @@ func TestSCEP_ValidateChallenge(t *testing.T) { }) } } + +func TestSCEP_Init(t *testing.T) { + serialize := func(key crypto.PrivateKey, password string) []byte { + var opts []pemutil.Options + if password == "" { + opts = append(opts, pemutil.WithPasswordPrompt("no password", func(s string) ([]byte, error) { + return nil, nil + })) + } else { + opts = append(opts, pemutil.WithPassword([]byte("password"))) + } + block, err := pemutil.Serialize(key, opts...) + require.NoError(t, err) + return pem.EncodeToMemory(block) + } + + ca, err := minica.New() + require.NoError(t, err) + + key, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + badKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + cert, err := ca.Sign(&x509.Certificate{ + Subject: pkix.Name{CommonName: "SCEP decryptor"}, + PublicKey: key.Public(), + }) + require.NoError(t, err) + + certPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", Bytes: cert.Raw, + }) + certPEMWithIntermediate := append(pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", Bytes: cert.Raw, + }), pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", Bytes: ca.Intermediate.Raw, + })...) + + keyPEM := serialize(key, "password") + keyPEMNoPassword := serialize(key, "") + badKeyPEM := serialize(badKey, "password") + + tmp := t.TempDir() + path := filepath.Join(tmp, "rsa.priv") + pathNoPassword := filepath.Join(tmp, "rsa.key") + + require.NoError(t, os.WriteFile(path, keyPEM, 0600)) + require.NoError(t, os.WriteFile(pathNoPassword, keyPEMNoPassword, 0600)) + + type args struct { + config Config + } + tests := []struct { + name string + s *SCEP + args args + wantErr bool + }{ + {"ok", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, false}, + {"ok no password", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: keyPEMNoPassword, + DecrypterKeyPassword: "", + EncryptionAlgorithmIdentifier: 1, + }, args{Config{Claims: globalProvisionerClaims}}, false}, + {"ok with uri", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 1024, + DecrypterCertificate: certPEM, + DecrypterKeyURI: "softkms:path=" + path, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 2, + }, args{Config{Claims: globalProvisionerClaims}}, false}, + {"ok with uri no password", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 2048, + DecrypterCertificate: certPEM, + DecrypterKeyURI: "softkms:path=" + pathNoPassword, + DecrypterKeyPassword: "", + EncryptionAlgorithmIdentifier: 3, + }, args{Config{Claims: globalProvisionerClaims}}, false}, + {"ok with SCEPKeyManager", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 2048, + DecrypterCertificate: certPEM, + DecrypterKeyURI: "softkms:path=" + pathNoPassword, + DecrypterKeyPassword: "", + EncryptionAlgorithmIdentifier: 4, + }, args{Config{Claims: globalProvisionerClaims, SCEPKeyManager: &softkms.SoftKMS{}}}, false}, + {"ok intermediate", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: nil, + DecrypterKeyPEM: nil, + DecrypterKeyPassword: "", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, false}, + {"fail type", &SCEP{ + Type: "", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail name", &SCEP{ + Type: "SCEP", + Name: "", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail minimumPublicKeyLength", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 2001, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail encryptionAlgorithmIdentifier", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 5, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail negative encryptionAlgorithmIdentifier", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: -1, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail key decode", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: []byte("not a pem"), + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail certificate decode", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: []byte("not a pem"), + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail certificate with intermediate", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEMWithIntermediate, + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "password", + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail decrypter password", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "badpassword", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail uri", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyURI: "softkms:path=missing.key", + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail uri password", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyURI: "softkms:path=" + path, + DecrypterKeyPassword: "badpassword", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail uri type", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyURI: "foo:path=" + path, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail missing certificate", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: nil, + DecrypterKeyPEM: keyPEM, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + {"fail key match", &SCEP{ + Type: "SCEP", + Name: "scep", + ChallengePassword: "password123", + MinimumPublicKeyLength: 0, + DecrypterCertificate: certPEM, + DecrypterKeyPEM: badKeyPEM, + DecrypterKeyPassword: "password", + EncryptionAlgorithmIdentifier: 0, + }, args{Config{Claims: globalProvisionerClaims}}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.s.Init(tt.args.config); (err != nil) != tt.wantErr { + t.Errorf("SCEP.Init() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 997a175d..69708630 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -302,14 +302,19 @@ func (v defaultSANsValidator) Valid(req *x509.CertificateRequest) (err error) { // duration. type profileDefaultDuration time.Duration +// Modify sets the certificate NotBefore and NotAfter using the following order: +// - From the SignOptions that we get from flags. +// - From x509.Certificate that we get from the template. +// - NotBefore from the current time with a backdate. +// - NotAfter from NotBefore plus the duration in v. func (v profileDefaultDuration) Modify(cert *x509.Certificate, so SignOptions) error { var backdate time.Duration - notBefore := so.NotBefore.Time() + notBefore := timeOr(so.NotBefore.Time(), cert.NotBefore) if notBefore.IsZero() { notBefore = now() backdate = -1 * so.Backdate } - notAfter := so.NotAfter.RelativeTime(notBefore) + notAfter := timeOr(so.NotAfter.RelativeTime(notBefore), cert.NotAfter) if notAfter.IsZero() { if v != 0 { notAfter = notBefore.Add(time.Duration(v)) @@ -330,11 +335,17 @@ type profileLimitDuration struct { notBefore, notAfter time.Time } -// Option returns an x509util option that limits the validity period of a -// certificate to one that is superficially imposed. +// Modify sets the certificate NotBefore and NotAfter but limits the validity +// period to the certificate to one that is superficially imposed. +// +// The expected NotBefore and NotAfter are set using the following order: +// - From the SignOptions that we get from flags. +// - From x509.Certificate that we get from the template. +// - NotBefore from the current time with a backdate. +// - NotAfter from NotBefore plus the duration v or the notAfter in v if lower. func (v profileLimitDuration) Modify(cert *x509.Certificate, so SignOptions) error { var backdate time.Duration - notBefore := so.NotBefore.Time() + notBefore := timeOr(so.NotBefore.Time(), cert.NotBefore) if notBefore.IsZero() { notBefore = now() backdate = -1 * so.Backdate @@ -345,7 +356,7 @@ func (v profileLimitDuration) Modify(cert *x509.Certificate, so SignOptions) err notBefore, v.notBefore) } - notAfter := so.NotAfter.RelativeTime(notBefore) + notAfter := timeOr(so.NotAfter.RelativeTime(notBefore), cert.NotAfter) if notAfter.After(v.notAfter) { return errs.Forbidden( "requested certificate notAfter (%s) is after the expiration of the provisioning credential (%s)", @@ -372,8 +383,8 @@ type validityValidator struct { } // newValidityValidator return a new validity validator. -func newValidityValidator(min, max time.Duration) *validityValidator { - return &validityValidator{min: min, max: max} +func newValidityValidator(minDur, maxDur time.Duration) *validityValidator { + return &validityValidator{min: minDur, max: maxDur} } // Valid validates the certificate validity settings (notBefore/notAfter) and diff --git a/authority/provisioner/sign_options_test.go b/authority/provisioner/sign_options_test.go index 5a55aa86..4663c334 100644 --- a/authority/provisioner/sign_options_test.go +++ b/authority/provisioner/sign_options_test.go @@ -598,9 +598,61 @@ func Test_profileDefaultDuration_Option(t *testing.T) { na := time.Now().Add(10 * time.Minute).UTC() d := 4 * time.Hour return test{ - pdd: profileDefaultDuration(d), - so: SignOptions{NotBefore: NewTimeDuration(nb), NotAfter: NewTimeDuration(na)}, - cert: new(x509.Certificate), + pdd: profileDefaultDuration(d), + so: SignOptions{NotBefore: NewTimeDuration(nb), NotAfter: NewTimeDuration(na)}, + cert: &x509.Certificate{ + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + }, + valid: func(cert *x509.Certificate) { + assert.Equals(t, nb, cert.NotBefore) + assert.Equals(t, na, cert.NotAfter) + }, + } + }, + "ok/cert-with-validity": func() test { + nb := time.Now().Add(5 * time.Minute).UTC() + na := time.Now().Add(10 * time.Minute).UTC() + d := 4 * time.Hour + return test{ + pdd: profileDefaultDuration(d), + so: SignOptions{}, + cert: &x509.Certificate{ + NotBefore: nb, + NotAfter: na, + }, + valid: func(cert *x509.Certificate) { + assert.Equals(t, nb, cert.NotBefore) + assert.Equals(t, na, cert.NotAfter) + }, + } + }, + "ok/cert-notBefore-option-notafter": func() test { + nb := time.Now().Add(5 * time.Minute).UTC() + na := time.Now().Add(10 * time.Minute).UTC() + d := 4 * time.Hour + return test{ + pdd: profileDefaultDuration(d), + so: SignOptions{NotAfter: NewTimeDuration(na)}, + cert: &x509.Certificate{ + NotBefore: nb, + }, + valid: func(cert *x509.Certificate) { + assert.Equals(t, nb, cert.NotBefore) + assert.Equals(t, na, cert.NotAfter) + }, + } + }, + "ok/cert-notAfter-option-notBefore": func() test { + nb := time.Now().Add(5 * time.Minute).UTC() + na := time.Now().Add(10 * time.Minute).UTC() + d := 4 * time.Hour + return test{ + pdd: profileDefaultDuration(d), + so: SignOptions{NotBefore: NewTimeDuration(nb)}, + cert: &x509.Certificate{ + NotAfter: na, + }, valid: func(cert *x509.Certificate) { assert.Equals(t, cert.NotBefore, nb) assert.Equals(t, cert.NotAfter, na) @@ -725,6 +777,28 @@ func Test_profileLimitDuration_Option(t *testing.T) { err: errors.New("requested certificate notAfter ("), } }, + "fail/cert-validity-notBefore": func() test { + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n, notAfter: n.Add(6 * time.Hour)}, + so: SignOptions{}, + cert: &x509.Certificate{ + NotBefore: n.Add(-time.Second), + NotAfter: n.Add(5 * time.Hour), + }, + err: errors.New("requested certificate notBefore ("), + } + }, + "fail/cert-validity-notAfter": func() test { + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n, notAfter: n.Add(6 * time.Hour)}, + so: SignOptions{}, + cert: &x509.Certificate{ + NotBefore: n, + NotAfter: n.Add(6*time.Hour + time.Second), + }, + err: errors.New("requested certificate notAfter ("), + } + }, "ok/valid-notAfter-requested": func() test { d, err := ParseTimeDuration("2h") assert.FatalError(t, err) @@ -782,6 +856,72 @@ func Test_profileLimitDuration_Option(t *testing.T) { }, } }, + "ok/cert-validity": func() test { + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n, notAfter: n.Add(6 * time.Hour)}, + so: SignOptions{}, + cert: &x509.Certificate{ + NotBefore: n, + NotAfter: n.Add(5 * time.Hour), + }, + valid: func(cert *x509.Certificate) { + assert.Equals(t, n, cert.NotBefore) + assert.Equals(t, n.Add(5*time.Hour), cert.NotAfter) + }, + } + }, + "ok/cert-notBefore-default": func() test { + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n, notAfter: n.Add(6 * time.Hour)}, + so: SignOptions{}, + cert: &x509.Certificate{ + NotBefore: n, + }, + valid: func(cert *x509.Certificate) { + assert.Equals(t, n, cert.NotBefore) + assert.Equals(t, n.Add(4*time.Hour), cert.NotAfter) + }, + } + }, + "ok/cert-notAfter-default": func() test { + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n, notAfter: n.Add(6 * time.Hour)}, + so: SignOptions{}, + cert: &x509.Certificate{ + NotAfter: n.Add(5 * time.Hour), + }, + valid: func(cert *x509.Certificate) { + assert.Equals(t, n, cert.NotBefore) + assert.Equals(t, n.Add(5*time.Hour), cert.NotAfter) + }, + } + }, + "ok/cert-notBefore-option": func() test { + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n, notAfter: n.Add(6 * time.Hour)}, + so: SignOptions{NotAfter: NewTimeDuration(n.Add(5 * time.Hour))}, + cert: &x509.Certificate{ + NotBefore: n, + }, + valid: func(cert *x509.Certificate) { + assert.Equals(t, n, cert.NotBefore) + assert.Equals(t, n.Add(5*time.Hour), cert.NotAfter) + }, + } + }, + "ok/cert-notAfter-option": func() test { + return test{ + pld: profileLimitDuration{def: 4 * time.Hour, notBefore: n, notAfter: n.Add(6 * time.Hour)}, + so: SignOptions{NotBefore: NewTimeDuration(n.Add(4 * time.Hour))}, + cert: &x509.Certificate{ + NotAfter: n.Add(5 * time.Hour), + }, + valid: func(cert *x509.Certificate) { + assert.Equals(t, n.Add(4*time.Hour), cert.NotBefore) + assert.Equals(t, n.Add(5*time.Hour), cert.NotAfter) + }, + } + }, } for name, run := range tests { t.Run(name, func(t *testing.T) { diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index 648b4672..623ea6fb 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -286,14 +286,14 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti return errs.BadRequest("ssh certificate validBefore cannot be before validAfter") } - var min, max time.Duration + var minDur, maxDur time.Duration switch cert.CertType { case ssh.UserCert: - min = v.MinUserSSHCertDuration() - max = v.MaxUserSSHCertDuration() + minDur = v.MinUserSSHCertDuration() + maxDur = v.MaxUserSSHCertDuration() case ssh.HostCert: - min = v.MinHostSSHCertDuration() - max = v.MaxHostSSHCertDuration() + minDur = v.MinHostSSHCertDuration() + maxDur = v.MaxHostSSHCertDuration() case 0: return errs.BadRequest("ssh certificate type has not been set") default: @@ -305,10 +305,10 @@ func (v *sshCertValidityValidator) Valid(cert *ssh.Certificate, opts SignSSHOpti dur := time.Duration(cert.ValidBefore-cert.ValidAfter) * time.Second switch { - case dur < min: - return errs.Forbidden("requested duration of %s is less than minimum accepted duration for selected provisioner of %s", dur, min) - case dur > max+opts.Backdate: - return errs.Forbidden("requested duration of %s is greater than maximum accepted duration for selected provisioner of %s", dur, max+opts.Backdate) + case dur < minDur: + return errs.Forbidden("requested duration of %s is less than minimum accepted duration for selected provisioner of %s", dur, minDur) + case dur > maxDur+opts.Backdate: + return errs.Forbidden("requested duration of %s is greater than maximum accepted duration for selected provisioner of %s", dur, maxDur+opts.Backdate) default: return nil } diff --git a/authority/provisioner/timeduration.go b/authority/provisioner/timeduration.go index 7d197217..39bbb192 100644 --- a/authority/provisioner/timeduration.go +++ b/authority/provisioner/timeduration.go @@ -11,6 +11,17 @@ var now = func() time.Time { return time.Now().UTC() } +// timeOr returns the first of its arguments that is not equal to the zero time. +// This method can be replaced with cmp.Or when step-ca requires Go 1.22. +func timeOr(ts ...time.Time) time.Time { + for _, t := range ts { + if !t.IsZero() { + return t + } + } + return time.Time{} +} + // TimeDuration is a type that represents a time but the JSON unmarshaling can // use a time using the RFC 3339 format or a time.Duration string. If a duration // is used, the time will be set on the first call to TimeDuration.Time. diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index 18538d0c..e455f5f5 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -363,11 +363,13 @@ func generateGCP() (*GCP, error) { return nil, err } p := &GCP{ - Type: "GCP", - Name: name, - ServiceAccounts: []string{serviceAccount}, - Claims: &globalProvisionerClaims, - config: newGCPConfig(), + Type: "GCP", + Name: name, + ServiceAccounts: []string{serviceAccount}, + Claims: &globalProvisionerClaims, + DisableSSHCAHost: &DefaultDisableSSHCAHost, + DisableSSHCAUser: &DefaultDisableSSHCAUser, + config: newGCPConfig(), keyStore: &keyStore{ keySet: jose.JSONWebKeySet{Keys: []jose.JSONWebKey{*jwk}}, expiry: time.Now().Add(24 * time.Hour), diff --git a/authority/provisioners.go b/authority/provisioners.go index 34cc75ed..5fb9ff5b 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -201,6 +201,7 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner. AuthorizeRenewFunc: a.authorizeRenewFunc, AuthorizeSSHRenewFunc: a.authorizeSSHRenewFunc, WebhookClient: a.webhookClient, + SCEPKeyManager: a.scepKeyManager, }, nil } @@ -425,25 +426,25 @@ func ValidateClaims(c *linkedca.Claims) error { // ValidateDurations validates the Durations type. func ValidateDurations(d *linkedca.Durations) error { var ( - err error - min, max, def *provisioner.Duration + err error + minDur, maxDur, def *provisioner.Duration ) if d.Min != "" { - min, err = provisioner.NewDuration(d.Min) + minDur, err = provisioner.NewDuration(d.Min) if err != nil { return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' is invalid", d.Min) } - if min.Value() < 0 { + if minDur.Value() < 0 { return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' cannot be less than 0", d.Min) } } if d.Max != "" { - max, err = provisioner.NewDuration(d.Max) + maxDur, err = provisioner.NewDuration(d.Max) if err != nil { return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' is invalid", d.Max) } - if max.Value() < 0 { + if maxDur.Value() < 0 { return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' cannot be less than 0", d.Max) } } @@ -456,15 +457,15 @@ func ValidateDurations(d *linkedca.Durations) error { return admin.WrapError(admin.ErrorBadRequestType, err, "default duration '%s' cannot be less than 0", d.Default) } } - if d.Min != "" && d.Max != "" && min.Value() > max.Value() { + if d.Min != "" && d.Max != "" && minDur.Value() > maxDur.Value() { return admin.NewError(admin.ErrorBadRequestType, "min duration '%s' cannot be greater than max duration '%s'", d.Min, d.Max) } - if d.Min != "" && d.Default != "" && min.Value() > def.Value() { + if d.Min != "" && d.Default != "" && minDur.Value() > def.Value() { return admin.NewError(admin.ErrorBadRequestType, "min duration '%s' cannot be greater than default duration '%s'", d.Min, d.Default) } - if d.Default != "" && d.Max != "" && min.Value() > def.Value() { + if d.Default != "" && d.Max != "" && minDur.Value() > def.Value() { return admin.NewError(admin.ErrorBadRequestType, "default duration '%s' cannot be greater than max duration '%s'", d.Default, d.Max) } @@ -607,15 +608,15 @@ func provisionerWebhookToLinkedca(pwh *provisioner.Webhook) *linkedca.Webhook { return lwh } -func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.Duration, err error) { +func durationsToCertificates(d *linkedca.Durations) (minDur, maxDur, def *provisioner.Duration, err error) { if d.Min != "" { - min, err = provisioner.NewDuration(d.Min) + minDur, err = provisioner.NewDuration(d.Min) if err != nil { return nil, nil, nil, admin.WrapErrorISE(err, "error parsing minimum duration '%s'", d.Min) } } if d.Max != "" { - max, err = provisioner.NewDuration(d.Max) + maxDur, err = provisioner.NewDuration(d.Max) if err != nil { return nil, nil, nil, admin.WrapErrorISE(err, "error parsing maximum duration '%s'", d.Max) } @@ -917,6 +918,8 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, Domains: cfg.Domains, Groups: cfg.Groups, ListenAddress: cfg.ListenAddress, + Scopes: cfg.Scopes, + AuthParams: cfg.AuthParams, Claims: claims, Options: options, }, nil @@ -1065,6 +1068,8 @@ func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, erro Groups: p.Groups, ListenAddress: p.ListenAddress, TenantId: p.TenantID, + Scopes: p.Scopes, + AuthParams: p.AuthParams, }, }, }, diff --git a/authority/root.go b/authority/root.go index 37038cfa..0a6ee639 100644 --- a/authority/root.go +++ b/authority/root.go @@ -57,3 +57,26 @@ func (a *Authority) GetFederation() (federation []*x509.Certificate, err error) }) return } + +// GetIntermediateCertificate return the intermediate certificate that issues +// the leaf certificates in the CA. +// +// This method can return nil if the CA is configured with a Certificate +// Authority Service (CAS) that does not implement the +// CertificateAuthorityGetter interface. +func (a *Authority) GetIntermediateCertificate() *x509.Certificate { + if len(a.intermediateX509Certs) > 0 { + return a.intermediateX509Certs[0] + } + return nil +} + +// GetIntermediateCertificates returns a list of all intermediate certificates +// configured. The first certificate in the list will be the issuer certificate. +// +// This method can return an empty list or nil if the CA is configured with a +// Certificate Authority Service (CAS) that does not implement the +// CertificateAuthorityGetter interface. +func (a *Authority) GetIntermediateCertificates() []*x509.Certificate { + return a.intermediateX509Certs +} diff --git a/authority/root_test.go b/authority/root_test.go index e570b0be..a0811dd2 100644 --- a/authority/root_test.go +++ b/authority/root_test.go @@ -2,15 +2,18 @@ package authority import ( "crypto/x509" + "crypto/x509/pkix" "errors" "net/http" "reflect" "testing" - "go.step.sm/crypto/pemutil" - "github.com/smallstep/assert" "github.com/smallstep/certificates/api/render" + "github.com/stretchr/testify/require" + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/minica" + "go.step.sm/crypto/pemutil" ) func TestRoot(t *testing.T) { @@ -152,3 +155,63 @@ func TestAuthority_GetFederation(t *testing.T) { }) } } + +func TestAuthority_GetIntermediateCertificate(t *testing.T) { + ca, err := minica.New(minica.WithRootTemplate(`{ + "subject": {{ toJson .Subject }}, + "issuer": {{ toJson .Subject }}, + "keyUsage": ["certSign", "crlSign"], + "basicConstraints": { + "isCA": true, + "maxPathLen": -1 + } + }`), minica.WithIntermediateTemplate(`{ + "subject": {{ toJson .Subject }}, + "keyUsage": ["certSign", "crlSign"], + "basicConstraints": { + "isCA": true, + "maxPathLen": 1 + } + }`)) + require.NoError(t, err) + + signer, err := keyutil.GenerateDefaultSigner() + require.NoError(t, err) + + cert, err := ca.Sign(&x509.Certificate{ + Subject: pkix.Name{CommonName: "MiniCA Intermediate CA 0"}, + PublicKey: signer.Public(), + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 0, + }) + require.NoError(t, err) + + type fields struct { + intermediateX509Certs []*x509.Certificate + } + tests := []struct { + name string + fields fields + want *x509.Certificate + wantSlice []*x509.Certificate + }{ + {"ok one", fields{[]*x509.Certificate{ca.Intermediate}}, ca.Intermediate, []*x509.Certificate{ca.Intermediate}}, + {"ok multiple", fields{[]*x509.Certificate{cert, ca.Intermediate}}, cert, []*x509.Certificate{cert, ca.Intermediate}}, + {"ok empty", fields{[]*x509.Certificate{}}, nil, []*x509.Certificate{}}, + {"ok nil", fields{nil}, nil, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &Authority{ + intermediateX509Certs: tt.fields.intermediateX509Certs, + } + if got := a.GetIntermediateCertificate(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Authority.GetIntermediateCertificate() = %v, want %v", got, tt.want) + } + if got := a.GetIntermediateCertificates(); !reflect.DeepEqual(got, tt.wantSlice) { + t.Errorf("Authority.GetIntermediateCertificates() = %v, want %v", got, tt.wantSlice) + } + }) + } +} diff --git a/authority/tls.go b/authority/tls.go index ebc9d0d8..679c28ac 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -91,6 +91,21 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { } } +// GetX509Signer returns a [crypto.Signer] implementation using the intermediate +// key. +// +// This method can return a [NotImplementedError] if the CA is configured with a +// Certificate Authority Service (CAS) that does not implement the +// CertificateAuthoritySigner interface. +// +// [NotImplementedError]: https://pkg.go.dev/github.com/smallstep/certificates/cas/apiv1#NotImplementedError +func (a *Authority) GetX509Signer() (crypto.Signer, error) { + if s, ok := a.x509CAService.(casapi.CertificateAuthoritySigner); ok { + return s.GetSigner() + } + return nil, casapi.NotImplementedError{} +} + // Sign creates a signed certificate from a certificate signing request. It // creates a new context.Context, and calls into SignWithContext. // diff --git a/authority/tls_test.go b/authority/tls_test.go index 70bcd45f..e949c685 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -30,6 +30,7 @@ import ( "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/policy" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/softcas" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" @@ -240,7 +241,7 @@ func (e *testEnforcer) Enforce(cert *x509.Certificate) error { return nil } -func assertHasPrefix(t *testing.T, s, p string, msg ...interface{}) bool { +func assertHasPrefix(t *testing.T, s, p string) bool { t.Helper() return assert.True(t, strings.HasPrefix(s, p), "%q is not a prefix of %q", p, s) } @@ -424,7 +425,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -453,7 +454,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -482,7 +483,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -500,7 +501,7 @@ ZYtQ9Ot36qc= aa := testAuthority(t) aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -525,7 +526,7 @@ ZYtQ9Ot36qc= })) aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -545,7 +546,7 @@ ZYtQ9Ot36qc= aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { fmt.Println(crt.Subject) - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -643,7 +644,7 @@ ZYtQ9Ot36qc= _a := testAuthority(t) _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -677,7 +678,7 @@ ZYtQ9Ot36qc= _a := testAuthority(t) _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -711,7 +712,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -745,7 +746,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -782,7 +783,7 @@ ZYtQ9Ot36qc= _a.config.AuthorityConfig.Template = &ASN1DN{} _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject, pkix.Name{}) + assert.Equal(t, pkix.Name{}, crt.Subject) return nil }, } @@ -807,8 +808,8 @@ ZYtQ9Ot36qc= aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") - assert.Equal(t, crt.CRLDistributionPoints, []string{"http://ca.example.org/leaf.crl"}) + assert.Equal(t, "smallstep test", crt.Subject.CommonName) + assert.Equal(t, []string{"http://ca.example.org/leaf.crl"}, crt.CRLDistributionPoints) return nil }, } @@ -866,8 +867,8 @@ ZYtQ9Ot36qc= }, p.AttestationData()) } if assert.Len(t, certs, 2) { - assert.Equal(t, certs[0].Subject.CommonName, "smallstep test") - assert.Equal(t, certs[1].Subject.CommonName, "smallstep Intermediate CA") + assert.Equal(t, "smallstep test", certs[0].Subject.CommonName) + assert.Equal(t, "smallstep Intermediate CA", certs[1].Subject.CommonName) } return nil }, @@ -935,45 +936,44 @@ ZYtQ9Ot36qc= assert.Nil(t, certChain) var sc render.StatusCodedError require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equal(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equal(t, ctxErr.Details["csr"], tc.csr) - assert.Equal(t, ctxErr.Details["signOptions"], tc.signOpts) + assert.Equal(t, tc.csr, ctxErr.Details["csr"]) + assert.Equal(t, tc.signOpts, ctxErr.Details["signOptions"]) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - assert.Equal(t, leaf.NotBefore, tc.notBefore) - assert.Equal(t, leaf.NotAfter, tc.notAfter) + assert.Equal(t, tc.notBefore, leaf.NotBefore) + assert.Equal(t, tc.notAfter, leaf.NotAfter) tmplt := a.config.AuthorityConfig.Template if tc.csr.Subject.CommonName == "" { - assert.Equal(t, leaf.Subject, pkix.Name{}) + assert.Equal(t, pkix.Name{}, leaf.Subject) } else { - assert.Equal(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: "smallstep test", - }.String()) - assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com"}) + assert.Equal(t, pkix.Name{ + Country: []string{tmplt.Country}, + Organization: []string{tmplt.Organization}, + Locality: []string{tmplt.Locality}, + StreetAddress: []string{tmplt.StreetAddress}, + Province: []string{tmplt.Province}, + CommonName: "smallstep test", + }.String(), leaf.Subject.String()) + assert.Equal(t, []string{"test.smallstep.com"}, leaf.DNSNames) } - assert.Equal(t, leaf.Issuer, intermediate.Subject) - assert.Equal(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - assert.Equal(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - assert.Equal(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) + assert.Equal(t, intermediate.Subject, leaf.Issuer) + assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm) + assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm) + assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage) issuer := getDefaultIssuer(a) subjectKeyID, err := generateSubjectKeyID(pub) require.NoError(t, err) - assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) - assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, subjectKeyID, leaf.SubjectKeyId) + assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId) // Verify Provisioner OID found := 0 @@ -984,9 +984,9 @@ ZYtQ9Ot36qc= val := stepProvisionerASN1{} _, err := asn1.Unmarshal(ext.Value, &val) require.NoError(t, err) - assert.Equal(t, val.Type, provisionerTypeJWK) - assert.Equal(t, val.Name, []byte(p.Name)) - assert.Equal(t, val.CredentialID, []byte(p.Key.KeyID)) + assert.Equal(t, provisionerTypeJWK, val.Type) + assert.Equal(t, []byte(p.Name), val.Name) + assert.Equal(t, []byte(p.Key.KeyID), val.CredentialID) // Basic Constraints case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 19})): @@ -1008,7 +1008,7 @@ ZYtQ9Ot36qc= assert.Equal(t, found, 1) realIntermediate, err := x509.ParseCertificate(issuer.Raw) require.NoError(t, err) - assert.Equal(t, intermediate, realIntermediate) + assert.Equal(t, realIntermediate, intermediate) assert.Len(t, leaf.Extensions, tc.extensionsCount) } } @@ -1152,18 +1152,18 @@ func TestAuthority_Renew(t *testing.T) { assert.Nil(t, certChain) var sc render.StatusCodedError require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equal(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equal(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) + assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"]) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - assert.Equal(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) + assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore)) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) @@ -1173,30 +1173,29 @@ func TestAuthority_Renew(t *testing.T) { assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) tmplt := a.config.AuthorityConfig.Template - assert.Equal(t, leaf.RawSubject, tc.cert.RawSubject) - assert.Equal(t, leaf.Subject.Country, []string{tmplt.Country}) - assert.Equal(t, leaf.Subject.Organization, []string{tmplt.Organization}) - assert.Equal(t, leaf.Subject.Locality, []string{tmplt.Locality}) - assert.Equal(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress}) - assert.Equal(t, leaf.Subject.Province, []string{tmplt.Province}) - assert.Equal(t, leaf.Subject.CommonName, tmplt.CommonName) + assert.Equal(t, tc.cert.RawSubject, leaf.RawSubject) + assert.Equal(t, []string{tmplt.Country}, leaf.Subject.Country) + assert.Equal(t, []string{tmplt.Organization}, leaf.Subject.Organization) + assert.Equal(t, []string{tmplt.Locality}, leaf.Subject.Locality) + assert.Equal(t, []string{tmplt.StreetAddress}, leaf.Subject.StreetAddress) + assert.Equal(t, []string{tmplt.Province}, leaf.Subject.Province) + assert.Equal(t, tmplt.CommonName, leaf.Subject.CommonName) - assert.Equal(t, leaf.Issuer, intermediate.Subject) + assert.Equal(t, intermediate.Subject, leaf.Issuer) - assert.Equal(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - assert.Equal(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - assert.Equal(t, leaf.ExtKeyUsage, - []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) - assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) + assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm) + assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm) + assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage) + assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames) subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey) require.NoError(t, err) - assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) + assert.Equal(t, subjectKeyID, leaf.SubjectKeyId) // We did not change the intermediate before renewing. authIssuer := getDefaultIssuer(tc.auth) if issuer.SerialNumber == authIssuer.SerialNumber { - assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1216,7 +1215,7 @@ func TestAuthority_Renew(t *testing.T) { } } else { // We did change the intermediate before renewing. - assert.Equal(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) + assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1245,7 +1244,7 @@ func TestAuthority_Renew(t *testing.T) { realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) require.NoError(t, err) - assert.Equal(t, intermediate, realIntermediate) + assert.Equal(t, realIntermediate, intermediate) } } }) @@ -1357,18 +1356,18 @@ func TestAuthority_Rekey(t *testing.T) { assert.Nil(t, certChain) var sc render.StatusCodedError require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equal(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equal(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) + assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"]) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - assert.Equal(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) + assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore)) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) @@ -1378,41 +1377,39 @@ func TestAuthority_Rekey(t *testing.T) { assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) tmplt := a.config.AuthorityConfig.Template - assert.Equal(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.Equal(t, leaf.Issuer, intermediate.Subject) + assert.Equal(t, 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(), leaf.Subject.String()) + assert.Equal(t, intermediate.Subject, leaf.Issuer) - assert.Equal(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - assert.Equal(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - assert.Equal(t, leaf.ExtKeyUsage, - []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) - assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) + assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm) + assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm) + assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage) + assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames) // Test Public Key and SubjectKeyId expectedPK := tc.pk if tc.pk == nil { expectedPK = cert.PublicKey } - assert.Equal(t, leaf.PublicKey, expectedPK) + assert.Equal(t, expectedPK, leaf.PublicKey) subjectKeyID, err := generateSubjectKeyID(expectedPK) require.NoError(t, err) - assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) + assert.Equal(t, subjectKeyID, leaf.SubjectKeyId) if tc.pk == nil { - assert.Equal(t, leaf.SubjectKeyId, cert.SubjectKeyId) + assert.Equal(t, cert.SubjectKeyId, leaf.SubjectKeyId) } // We did not change the intermediate before renewing. authIssuer := getDefaultIssuer(tc.auth) if issuer.SerialNumber == authIssuer.SerialNumber { - assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1432,7 +1429,7 @@ func TestAuthority_Rekey(t *testing.T) { } } else { // We did change the intermediate before renewing. - assert.Equal(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) + assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1461,7 +1458,7 @@ func TestAuthority_Rekey(t *testing.T) { realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) require.NoError(t, err) - assert.Equal(t, intermediate, realIntermediate) + assert.Equal(t, realIntermediate, intermediate) } } }) @@ -1499,7 +1496,7 @@ func TestAuthority_GetTLSOptions(t *testing.T) { require.NoError(t, err) opts := tc.auth.GetTLSOptions() - assert.Equal(t, opts, tc.opts) + assert.Equal(t, tc.opts, opts) }) } } @@ -1569,9 +1566,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("authority.Revoke; no persistence layer configured"), code: http.StatusNotImplemented, checkErrDetails: func(err *errs.Error) { - assert.Equal(t, err.Details["token"], raw) - assert.Equal(t, err.Details["tokenID"], "44") - assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, raw, err.Details["token"]) + assert.Equal(t, "44", err.Details["tokenID"]) + assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"]) }, } }, @@ -1609,9 +1606,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("authority.Revoke: force"), code: http.StatusInternalServerError, checkErrDetails: func(err *errs.Error) { - assert.Equal(t, err.Details["token"], raw) - assert.Equal(t, err.Details["tokenID"], "44") - assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, raw, err.Details["token"]) + assert.Equal(t, "44", err.Details["tokenID"]) + assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"]) }, } }, @@ -1649,9 +1646,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("certificate with serial number 'sn' is already revoked"), code: http.StatusBadRequest, checkErrDetails: func(err *errs.Error) { - assert.Equal(t, err.Details["token"], raw) - assert.Equal(t, err.Details["tokenID"], "44") - assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, raw, err.Details["token"]) + assert.Equal(t, "44", err.Details["tokenID"]) + assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"]) }, } }, @@ -1786,16 +1783,16 @@ func TestAuthority_Revoke(t *testing.T) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { var sc render.StatusCodedError require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equal(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equal(t, ctxErr.Details["serialNumber"], tc.opts.Serial) - assert.Equal(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode) - assert.Equal(t, ctxErr.Details["reason"], tc.opts.Reason) - assert.Equal(t, ctxErr.Details["MTLS"], tc.opts.MTLS) - assert.Equal(t, ctxErr.Details["context"], provisioner.RevokeMethod.String()) + assert.Equal(t, tc.opts.Serial, ctxErr.Details["serialNumber"]) + assert.Equal(t, tc.opts.ReasonCode, ctxErr.Details["reasonCode"]) + assert.Equal(t, tc.opts.Reason, ctxErr.Details["reason"]) + assert.Equal(t, tc.opts.MTLS, ctxErr.Details["MTLS"]) + assert.Equal(t, provisioner.RevokeMethod.String(), ctxErr.Details["context"]) if tc.checkErrDetails != nil { tc.checkErrDetails(ctxErr) @@ -2033,3 +2030,39 @@ func TestAuthority_CRL(t *testing.T) { }) } } + +type notImplementedCAS struct{} + +func (notImplementedCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { + return nil, apiv1.NotImplementedError{} +} +func (notImplementedCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { + return nil, apiv1.NotImplementedError{} +} +func (notImplementedCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { + return nil, apiv1.NotImplementedError{} +} + +func TestAuthority_GetX509Signer(t *testing.T) { + auth := testAuthority(t) + require.IsType(t, &softcas.SoftCAS{}, auth.x509CAService) + signer := auth.x509CAService.(*softcas.SoftCAS).Signer + require.NotNil(t, signer) + + tests := []struct { + name string + authority *Authority + want crypto.Signer + assertion assert.ErrorAssertionFunc + }{ + {"ok", auth, signer, assert.NoError}, + {"fail", testAuthority(t, WithX509CAService(notImplementedCAS{})), nil, assert.Error}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.authority.GetX509Signer() + tt.assertion(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/ca/acmeClient_test.go b/ca/acmeClient_test.go index 77d380f9..b2c6b41a 100644 --- a/ca/acmeClient_test.go +++ b/ca/acmeClient_test.go @@ -108,19 +108,19 @@ func TestNewACMEClient(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header switch { case i == 0: - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ case i == 1: w.Header().Set("Replay-Nonce", "abc123") - render.JSONStatus(w, []byte{}, 200) + render.JSONStatus(w, r, []byte{}, 200) i++ default: w.Header().Set("Location", accLocation) - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) } }) @@ -203,10 +203,10 @@ func TestACMEClient_GetNonce(t *testing.T) { t.Run(name, func(t *testing.T) { tc := run(t) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) }) if nonce, err := ac.GetNonce(); err != nil { @@ -310,18 +310,18 @@ func TestACMEClient_post(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) if i == 0 { - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) assert.FatalError(t, err) @@ -338,7 +338,7 @@ func TestACMEClient_post(t *testing.T) { assert.Equals(t, hdr.KeyID, ac.kid) } - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) }) if resp, err := tc.client.post(tc.payload, url, tc.ops...); err != nil { @@ -450,18 +450,18 @@ func TestACMEClient_NewOrder(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) if i == 0 { - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) assert.FatalError(t, err) @@ -477,7 +477,7 @@ func TestACMEClient_NewOrder(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, payload, norb) - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) }) if res, err := ac.NewOrder(norb); err != nil { @@ -572,18 +572,18 @@ func TestACMEClient_GetOrder(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) if i == 0 { - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) assert.FatalError(t, err) @@ -599,7 +599,7 @@ func TestACMEClient_GetOrder(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, len(payload), 0) - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) }) if res, err := ac.GetOrder(url); err != nil { @@ -694,18 +694,18 @@ func TestACMEClient_GetAuthz(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) if i == 0 { - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) assert.FatalError(t, err) @@ -721,7 +721,7 @@ func TestACMEClient_GetAuthz(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, len(payload), 0) - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) }) if res, err := ac.GetAuthz(url); err != nil { @@ -816,18 +816,18 @@ func TestACMEClient_GetChallenge(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) if i == 0 { - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) assert.FatalError(t, err) @@ -844,7 +844,7 @@ func TestACMEClient_GetChallenge(t *testing.T) { assert.Equals(t, len(payload), 0) - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) }) if res, err := ac.GetChallenge(url); err != nil { @@ -939,18 +939,18 @@ func TestACMEClient_ValidateChallenge(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) if i == 0 { - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) assert.FatalError(t, err) @@ -967,7 +967,7 @@ func TestACMEClient_ValidateChallenge(t *testing.T) { assert.Equals(t, payload, []byte("{}")) - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) }) if err := ac.ValidateChallenge(url); err != nil { @@ -983,22 +983,22 @@ func TestACMEClient_ValidateWithPayload(t *testing.T) { key, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) assert.FatalError(t, err) - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header - t.Log(req.RequestURI) + t.Log(r.RequestURI) w.Header().Set("Replay-Nonce", "nonce") - switch req.RequestURI { + switch r.RequestURI { case "/nonce": - render.JSONStatus(w, []byte{}, 200) + render.JSONStatus(w, r, []byte{}, 200) return case "/fail-nonce": - render.JSONStatus(w, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400) + render.JSONStatus(w, r, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400) return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) @@ -1015,15 +1015,15 @@ func TestACMEClient_ValidateWithPayload(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, payload, []byte("the-payload")) - switch req.RequestURI { + switch r.RequestURI { case "/ok": - render.JSONStatus(w, acme.Challenge{ + render.JSONStatus(w, r, acme.Challenge{ Type: "device-attestation-01", Status: "valid", Token: "foo", }, 200) case "/fail": - render.JSONStatus(w, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400) + render.JSONStatus(w, r, acme.NewError(acme.ErrorMalformedType, "malformed request"), 400) } })) defer srv.Close() @@ -1160,18 +1160,18 @@ func TestACMEClient_FinalizeOrder(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) if i == 0 { - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) assert.FatalError(t, err) @@ -1187,7 +1187,7 @@ func TestACMEClient_FinalizeOrder(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, payload, frb) - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) }) if err := ac.FinalizeOrder(url, csr); err != nil { @@ -1289,18 +1289,18 @@ func TestACMEClient_GetAccountOrders(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) if i == 0 { - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) assert.FatalError(t, err) @@ -1316,7 +1316,7 @@ func TestACMEClient_GetAccountOrders(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, len(payload), 0) - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) }) if res, err := tc.client.GetAccountOrders(); err != nil { @@ -1420,18 +1420,18 @@ func TestACMEClient_GetCertificate(t *testing.T) { tc := run(t) i := 0 - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - assert.Equals(t, "step-http-client/1.0", req.Header.Get("User-Agent")) // check default User-Agent header + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equals(t, "step-http-client/1.0", r.Header.Get("User-Agent")) // check default User-Agent header w.Header().Set("Replay-Nonce", expectedNonce) if i == 0 { - render.JSONStatus(w, tc.r1, tc.rc1) + render.JSONStatus(w, r, tc.r1, tc.rc1) i++ return } // validate jws request protected headers and body - body, err := io.ReadAll(req.Body) + body, err := io.ReadAll(r.Body) assert.FatalError(t, err) jws, err := jose.ParseJWS(string(body)) assert.FatalError(t, err) @@ -1450,7 +1450,7 @@ func TestACMEClient_GetCertificate(t *testing.T) { if tc.certBytes != nil { w.Write(tc.certBytes) } else { - render.JSONStatus(w, tc.r2, tc.rc2) + render.JSONStatus(w, r, tc.r2, tc.rc2) } }) diff --git a/ca/bootstrap_test.go b/ca/bootstrap_test.go index 62c422d4..da37eee5 100644 --- a/ca/bootstrap_test.go +++ b/ca/bootstrap_test.go @@ -87,7 +87,7 @@ func startCAServer(configFile string) (*CA, string, error) { func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/version" { - render.JSON(w, api.VersionResponse{ + render.JSON(w, r, api.VersionResponse{ Version: "test", RequireClientAuthentication: true, }) @@ -102,7 +102,7 @@ func mTLSMiddleware(next http.Handler, nonAuthenticatedPaths ...string) http.Han } isMTLS := r.TLS != nil && len(r.TLS.PeerCertificates) > 0 if !isMTLS { - render.Error(w, errs.Unauthorized("missing peer certificate")) + render.Error(w, r, errs.Unauthorized("missing peer certificate")) } else { next.ServeHTTP(w, r) } @@ -412,7 +412,7 @@ func TestBootstrapClientServerRotation(t *testing.T) { //nolint:gosec // insecure test server server, err := BootstrapServer(context.Background(), token, &http.Server{ Addr: ":0", - Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) }), }, RequireAndVerifyClientCert()) @@ -531,7 +531,7 @@ func TestBootstrapClientServerFederation(t *testing.T) { //nolint:gosec // insecure test server server, err := BootstrapServer(context.Background(), token, &http.Server{ Addr: ":0", - Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) }), }, RequireAndVerifyClientCert(), AddFederationToClientCAs()) diff --git a/ca/client_test.go b/ca/client_test.go index 44d24c6e..bd05614b 100644 --- a/ca/client_test.go +++ b/ca/client_test.go @@ -177,8 +177,8 @@ func TestClient_Version(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - render.JSONStatus(w, tt.response, tt.responseCode) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Version() @@ -218,8 +218,8 @@ func TestClient_Health(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - render.JSONStatus(w, tt.response, tt.responseCode) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Health() @@ -262,12 +262,12 @@ func TestClient_Root(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { expected := "/root/" + tt.shasum - if req.RequestURI != expected { - t.Errorf("RequestURI = %s, want %s", req.RequestURI, expected) + if r.RequestURI != expected { + t.Errorf("RequestURI = %s, want %s", r.RequestURI, expected) } - render.JSONStatus(w, tt.response, tt.responseCode) + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Root(tt.shasum) @@ -323,12 +323,12 @@ func TestClient_Sign(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body := new(api.SignRequest) - if err := read.JSON(req.Body, body); err != nil { + if err := read.JSON(r.Body, body); err != nil { e, ok := tt.response.(error) require.True(t, ok, "response expected to be error type") - render.Error(w, e) + render.Error(w, r, e) return } else if !equalJSON(t, body, tt.request) { if tt.request == nil { @@ -339,7 +339,7 @@ func TestClient_Sign(t *testing.T) { t.Errorf("Client.Sign() request = %v, wants %v", body, tt.request) } } - render.JSONStatus(w, tt.response, tt.responseCode) + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Sign(tt.request) @@ -385,12 +385,12 @@ func TestClient_Revoke(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body := new(api.RevokeRequest) - if err := read.JSON(req.Body, body); err != nil { + if err := read.JSON(r.Body, body); err != nil { e, ok := tt.response.(error) require.True(t, ok, "response expected to be error type") - render.Error(w, e) + render.Error(w, r, e) return } else if !equalJSON(t, body, tt.request) { if tt.request == nil { @@ -401,7 +401,7 @@ func TestClient_Revoke(t *testing.T) { t.Errorf("Client.Revoke() request = %v, wants %v", body, tt.request) } } - render.JSONStatus(w, tt.response, tt.responseCode) + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Revoke(tt.request, nil) @@ -450,8 +450,8 @@ func TestClient_Renew(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - render.JSONStatus(w, tt.response, tt.responseCode) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Renew(nil) @@ -504,11 +504,11 @@ func TestClient_RenewWithToken(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.Header.Get("Authorization") != "Bearer token" { - render.JSONStatus(w, errs.InternalServer("force"), 500) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != "Bearer token" { + render.JSONStatus(w, r, errs.InternalServer("force"), 500) } else { - render.JSONStatus(w, tt.response, tt.responseCode) + render.JSONStatus(w, r, tt.response, tt.responseCode) } }) @@ -567,8 +567,8 @@ func TestClient_Rekey(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - render.JSONStatus(w, tt.response, tt.responseCode) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Rekey(tt.request, nil) @@ -619,11 +619,11 @@ func TestClient_Provisioners(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if req.RequestURI != tt.expectedURI { - t.Errorf("RequestURI = %s, want %s", req.RequestURI, tt.expectedURI) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI != tt.expectedURI { + t.Errorf("RequestURI = %s, want %s", r.RequestURI, tt.expectedURI) } - render.JSONStatus(w, tt.response, tt.responseCode) + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Provisioners(tt.args...) @@ -666,12 +666,12 @@ func TestClient_ProvisionerKey(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { expected := "/provisioners/" + tt.kid + "/encrypted-key" - if req.RequestURI != expected { - t.Errorf("RequestURI = %s, want %s", req.RequestURI, expected) + if r.RequestURI != expected { + t.Errorf("RequestURI = %s, want %s", r.RequestURI, expected) } - render.JSONStatus(w, tt.response, tt.responseCode) + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.ProvisionerKey(tt.kid) @@ -720,8 +720,8 @@ func TestClient_Roots(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - render.JSONStatus(w, tt.response, tt.responseCode) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Roots() @@ -769,8 +769,8 @@ func TestClient_Federation(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - render.JSONStatus(w, tt.response, tt.responseCode) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.Federation() @@ -820,8 +820,8 @@ func TestClient_SSHRoots(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - render.JSONStatus(w, tt.response, tt.responseCode) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.SSHRoots() @@ -912,8 +912,8 @@ func TestClient_RootFingerprint(t *testing.T) { c, err := NewClient(tt.server.URL, WithTransport(tr)) require.NoError(t, err) - tt.server.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - render.JSONStatus(w, tt.response, tt.responseCode) + tt.server.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.RootFingerprint() @@ -970,8 +970,8 @@ func TestClient_SSHBastion(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) require.NoError(t, err) - srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - render.JSONStatus(w, tt.response, tt.responseCode) + srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + render.JSONStatus(w, r, tt.response, tt.responseCode) }) got, err := c.SSHBastion(tt.request) diff --git a/cas/apiv1/requests.go b/cas/apiv1/requests.go index fdbb285e..35cf9251 100644 --- a/cas/apiv1/requests.go +++ b/cas/apiv1/requests.go @@ -116,7 +116,8 @@ type GetCertificateAuthorityRequest struct { // GetCertificateAuthorityResponse is the response that contains // the root certificate. type GetCertificateAuthorityResponse struct { - RootCertificate *x509.Certificate + RootCertificate *x509.Certificate + IntermediateCertificates []*x509.Certificate } // CreateKeyRequest is the request used to generate a new key using a KMS. diff --git a/cas/apiv1/services.go b/cas/apiv1/services.go index 00ecc2a8..37c5adba 100644 --- a/cas/apiv1/services.go +++ b/cas/apiv1/services.go @@ -1,6 +1,7 @@ package apiv1 import ( + "crypto" "crypto/x509" "net/http" "strings" @@ -26,13 +27,20 @@ type CertificateAuthorityGetter interface { GetCertificateAuthority(req *GetCertificateAuthorityRequest) (*GetCertificateAuthorityResponse, error) } -// CertificateAuthorityCreator is an interface implamented by a +// CertificateAuthorityCreator is an interface implemented by a // CertificateAuthorityService that has a method to create a new certificate // authority. type CertificateAuthorityCreator interface { CreateCertificateAuthority(req *CreateCertificateAuthorityRequest) (*CreateCertificateAuthorityResponse, error) } +// CertificateAuthoritySigner is an optional interface implemented by a +// CertificateAuthorityService that has a method that returns a [crypto.Signer] +// using the same key used to issue certificates. +type CertificateAuthoritySigner interface { + GetSigner() (crypto.Signer, error) +} + // SignatureAlgorithmGetter is an optional implementation in a crypto.Signer // that returns the SignatureAlgorithm to use. type SignatureAlgorithmGetter interface { diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index 6e5d2133..6db5d75d 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -248,6 +248,7 @@ func mustParseECKey(t *testing.T, pemKey string) *ecdsa.PrivateKey { block, _ := pem.Decode([]byte(pemKey)) if block == nil { t.Fatal("failed to parse key") + return nil } key, err := x509.ParseECPrivateKey(block.Bytes) if err != nil { @@ -882,7 +883,7 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { defer srv.Stop() // Create fake privateca client - conn, err := grpc.DialContext(context.Background(), "", grpc.WithTransportCredentials(insecure.NewCredentials()), + conn, err := grpc.NewClient("localhost", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return lis.Dial() })) diff --git a/cas/softcas/softcas.go b/cas/softcas/softcas.go index dd961975..1e590eff 100644 --- a/cas/softcas/softcas.go +++ b/cas/softcas/softcas.go @@ -58,6 +58,13 @@ func (c *SoftCAS) Type() apiv1.Type { return apiv1.SoftCAS } +// GetSigner implements [apiv1.CertificateAuthoritySigner] and returns a +// [crypto.Signer] with the intermediate key. +func (c *SoftCAS) GetSigner() (crypto.Signer, error) { + _, signer, err := c.getCertSigner() + return signer, err +} + // CreateCertificate signs a new certificate using Golang or KMS crypto. func (c *SoftCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { switch { diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 8c04de3a..1c20d277 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -19,8 +19,11 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.step.sm/crypto/kms" kmsapi "go.step.sm/crypto/kms/apiv1" + "go.step.sm/crypto/minica" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" ) @@ -269,6 +272,45 @@ func TestSoftCAS_Type(t *testing.T) { } } +func TestSoftCAS_GetSigner(t *testing.T) { + ca, err := minica.New() + require.NoError(t, err) + + type fields struct { + CertificateChain []*x509.Certificate + Signer crypto.Signer + CertificateSigner func() ([]*x509.Certificate, crypto.Signer, error) + KeyManager kms.KeyManager + } + tests := []struct { + name string + fields fields + want crypto.Signer + assertion assert.ErrorAssertionFunc + }{ + {"ok signer", fields{[]*x509.Certificate{ca.Intermediate}, ca.Signer, nil, nil}, ca.Signer, assert.NoError}, + {"ok certificateSigner", fields{[]*x509.Certificate{ca.Intermediate}, nil, func() ([]*x509.Certificate, crypto.Signer, error) { + return []*x509.Certificate{ca.Intermediate}, ca.Signer, nil + }, nil}, ca.Signer, assert.NoError}, + {"fail certificateSigner", fields{[]*x509.Certificate{ca.Intermediate}, nil, func() ([]*x509.Certificate, crypto.Signer, error) { + return nil, nil, apiv1.NotImplementedError{} + }, nil}, nil, assert.Error}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SoftCAS{ + CertificateChain: tt.fields.CertificateChain, + Signer: tt.fields.Signer, + CertificateSigner: tt.fields.CertificateSigner, + KeyManager: tt.fields.KeyManager, + } + got, err := c.GetSigner() + tt.assertion(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + func TestSoftCAS_CreateCertificate(t *testing.T) { mockNow(t) // Set rand.Reader to EOF diff --git a/cas/vaultcas/vaultcas.go b/cas/vaultcas/vaultcas.go index 73d1b926..16adccc4 100644 --- a/cas/vaultcas/vaultcas.go +++ b/cas/vaultcas/vaultcas.go @@ -165,7 +165,8 @@ func (v *VaultCAS) GetCertificateAuthority(*apiv1.GetCertificateAuthorityRequest } return &apiv1.GetCertificateAuthorityResponse{ - RootCertificate: cert.root, + RootCertificate: cert.root, + IntermediateCertificates: cert.intermediates, }, nil } diff --git a/commands/app.go b/commands/app.go index 1ccaaf3c..c793f11c 100644 --- a/commands/app.go +++ b/commands/app.go @@ -83,6 +83,10 @@ Requires **--insecure** flag.`, Usage: `the used on tls-alpn-01 challenges. It can be changed for testing purposes. Requires **--insecure** flag.`, }, + cli.BoolFlag{ + Name: "acme-strict-fqdn", + Usage: `enable strict DNS resolution using a fully qualified domain name.`, + }, cli.StringFlag{ Name: "pidfile", Usage: "the path to the to write the process ID.", @@ -126,6 +130,9 @@ func appAction(ctx *cli.Context) error { } } + // Set the strict DNS resolution on ACME challenges. Defaults to false. + acme.StrictFQDN = ctx.Bool("acme-strict-fqdn") + // Allow custom contexts. if caCtx := ctx.String("context"); caCtx != "" { if _, ok := step.Contexts().Get(caCtx); ok { diff --git a/go.mod b/go.mod index c8162bd1..2e40c990 100644 --- a/go.mod +++ b/go.mod @@ -1,112 +1,114 @@ module github.com/smallstep/certificates -go 1.20 +go 1.21 require ( - cloud.google.com/go/longrunning v0.5.6 - cloud.google.com/go/security v1.15.6 + cloud.google.com/go/longrunning v0.5.10 + cloud.google.com/go/security v1.17.3 github.com/Masterminds/sprig/v3 v3.2.3 github.com/dgraph-io/badger v1.6.2 github.com/dgraph-io/badger/v2 v2.2007.4 - github.com/fxamacker/cbor/v2 v2.6.0 - github.com/go-chi/chi/v5 v5.0.12 + github.com/fxamacker/cbor/v2 v2.7.0 + github.com/go-chi/chi/v5 v5.1.0 github.com/go-jose/go-jose/v3 v3.0.3 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/google/go-tpm v0.9.0 github.com/google/uuid v1.6.0 - github.com/googleapis/gax-go/v2 v2.12.3 - github.com/hashicorp/vault/api v1.12.2 - github.com/hashicorp/vault/api/auth/approle v0.6.0 - github.com/hashicorp/vault/api/auth/kubernetes v0.6.0 - github.com/newrelic/go-agent/v3 v3.30.0 + github.com/googleapis/gax-go/v2 v2.13.0 + github.com/hashicorp/vault/api v1.14.0 + github.com/hashicorp/vault/api/auth/approle v0.7.0 + github.com/hashicorp/vault/api/auth/kubernetes v0.7.0 + github.com/newrelic/go-agent/v3 v3.33.1 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.19.0 + github.com/prometheus/client_golang v1.19.1 github.com/rs/xid v1.5.0 github.com/sirupsen/logrus v1.9.3 github.com/slackhq/nebula v1.6.1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 - github.com/smallstep/nosql v0.6.0 + github.com/smallstep/nosql v0.7.0 github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d github.com/stretchr/testify v1.9.0 - github.com/urfave/cli v1.22.14 + github.com/urfave/cli v1.22.15 go.step.sm/cli-utils v0.9.0 - go.step.sm/crypto v0.44.1 - go.step.sm/linkedca v0.20.1 - golang.org/x/crypto v0.21.0 - golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 - golang.org/x/net v0.22.0 - google.golang.org/api v0.171.0 - google.golang.org/grpc v1.62.1 - google.golang.org/protobuf v1.33.0 + go.step.sm/crypto v0.50.0 + go.step.sm/linkedca v0.22.1 + golang.org/x/crypto v0.25.0 + golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 + golang.org/x/net v0.27.0 + google.golang.org/api v0.189.0 + google.golang.org/grpc v1.65.0 + google.golang.org/protobuf v1.34.2 ) require ( - cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute v1.24.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.6 // indirect - cloud.google.com/go/kms v1.15.7 // indirect + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.7.2 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect + cloud.google.com/go/iam v1.1.10 // indirect + cloud.google.com/go/kms v1.18.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect - github.com/aws/aws-sdk-go-v2 v1.26.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.8 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.8 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 // indirect + github.com/aws/aws-sdk-go-v2 v1.30.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.24 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.24 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 // indirect - github.com/aws/smithy-go v1.20.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.35.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect + github.com/aws/smithy-go v1.20.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-kit/kit v0.13.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-piv/piv-go v1.11.0 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect - github.com/golang-jwt/jwt/v5 v5.2.0 // indirect - github.com/golang/glog v1.2.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang/glog v1.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/certificate-transparency-go v1.1.6 // indirect - github.com/google/go-tpm-tools v0.4.3 // indirect + github.com/google/go-tpm-tools v0.4.4 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.6.6 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect @@ -114,14 +116,10 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.3 // indirect - github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgtype v1.14.0 // indirect - github.com/jackc/pgx/v4 v4.18.3 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/klauspost/compress v1.16.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/manifoldco/promptui v0.9.0 // indirect @@ -147,21 +145,20 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/x448/float16 v0.8.4 // indirect - go.etcd.io/bbolt v1.3.7 // indirect + go.etcd.io/bbolt v1.3.10 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect + google.golang.org/genproto v0.0.0-20240722135656-d784300faade // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b5466034..0c6d1620 100644 --- a/go.sum +++ b/go.sum @@ -1,39 +1,40 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= -cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM= -cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= -cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE= -cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= -cloud.google.com/go/security v1.15.6 h1:LYMj7ISEEjVQ0ub6E6ygGhjVbNQTH5CawKZz0bbPMVE= -cloud.google.com/go/security v1.15.6/go.mod h1:UMEAGVBMqE6xZvkCR1FvUIeBEmGOCRIDwtwT357xmok= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/auth v0.7.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE= +cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= +cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= +cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI= +cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps= +cloud.google.com/go/kms v1.18.2 h1:EGgD0B9k9tOOkbPhYW1PHo2W0teamAUYMOUIcDRMfPk= +cloud.google.com/go/kms v1.18.2/go.mod h1:YFz1LYrnGsXARuRePL729oINmN5J/5e7nYijgvfiIeY= +cloud.google.com/go/longrunning v0.5.10 h1:eB/BniENNRKhjz/xgiillrdcH3G74TGSl3BXinGlI7E= +cloud.google.com/go/longrunning v0.5.10/go.mod h1:tljz5guTr5oc/qhlUjBlk7UAIFMOGuPNxkNDZXlLics= +cloud.google.com/go/security v1.17.3 h1:LTNqcCljAsgCEqZzir/+dSE8cEsc5wXKDQzTIoY9lxE= +cloud.google.com/go/security v1.17.3/go.mod h1:CuKzQq5OD6TXAYaZs/jI0d7CNHoD0LXbpsznIIIn4f4= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 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/sdk/azcore v1.10.0 h1:n1DH8TPV4qqPTje2RcUBYwtrTWlabVp4n46+74X2pn4= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0/go.mod h1:HDcZnuGbiyppErN6lB+idp4CKhjbc8gwjto6OPpyggM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= @@ -44,34 +45,34 @@ github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go-v2 v1.26.0 h1:/Ce4OCiM3EkpW7Y+xUnfAFpchU78K7/Ug01sZni9PgA= -github.com/aws/aws-sdk-go-v2 v1.26.0/go.mod h1:35hUlJVYd+M++iLI3ALmVwMOyRYMmRqUXpTtRGW+K9I= -github.com/aws/aws-sdk-go-v2/config v1.27.8 h1:0r8epOsiJ7YJz65MGcb8i91ehFp4kvvFe2qkq5oYeRI= -github.com/aws/aws-sdk-go-v2/config v1.27.8/go.mod h1:XsmYKxYNuIhLsFddpNds+j9H5XKzjWDdg/SZngiwFio= -github.com/aws/aws-sdk-go-v2/credentials v1.17.8 h1:WUdNLXbyNbU07V/WFrSOBXqZTDgmmMNMgUFzpYOKJhw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.8/go.mod h1:iPZzLpaBIfhyvVS/XGD3JvR1GP3YdHTqpySKDlqkfs8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.4 h1:S+L2QSKhUuShih3aq9P/mkzDBiOO5tTyVg+vXREfsfg= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.4/go.mod h1:nQ3how7DMnFMWiU1SpECohgC82fpn4cKZ875NDMmwtA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4 h1:0ScVK/4qZ8CIW0k8jOeFVsyS/sAiXpYxRBLolMkuLQM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.4/go.mod h1:84KyjNZdHC6QZW08nfHI6yZgPd+qRgaWcYsyLUo3QY8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4 h1:sHmMWWX5E7guWEFQ9SVo6A3S4xpPrWnd77a6y4WM6PU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.4/go.mod h1:WjpDrhWisWOIoS9n3nk67A3Ll1vfULJ9Kq6h29HTD48= +github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= +github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo= +github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M= +github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6 h1:b+E7zIUHMmcB4Dckjpkapoy47W6C9QBv/zoUP+Hn8Kc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.6/go.mod h1:S2fNV0rxrP78NhPbCZeQgY8H9jdDMeGtwcfZIRxzBqU= -github.com/aws/aws-sdk-go-v2/service/kms v1.30.0 h1:yS0JkEdV6h9JOo8sy2JSpjX+i7vsKifU8SIeHrqiDhU= -github.com/aws/aws-sdk-go-v2/service/kms v1.30.0/go.mod h1:+I8VUUSVD4p5ISQtzpgSva4I8cJ4SQ4b1dcBcof7O+g= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.3 h1:mnbuWHOcM70/OFUlZZ5rcdfA8PflGXXiefU/O+1S3+8= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.3/go.mod h1:5HFu51Elk+4oRBZVxmHrSds5jFXmFj8C3w7DVF2gnrs= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3 h1:uLq0BKatTmDzWa/Nu4WO0M1AaQDaPpwTKAeByEc6WFM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.3/go.mod h1:b+qdhjnxj8GSR6t5YfphOffeoQSQ1KmpoVVuBn+PWxs= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.5 h1:J/PpTf/hllOjx8Xu9DMflff3FajfLxqM5+tepvVXmxg= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.5/go.mod h1:0ih0Z83YDH/QeQ6Ori2yGE2XvWYv/Xm+cZc01LC6oK0= -github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= -github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.1 h1:0gP2OJJT6HM2BYltZ9x+A87OE8LJL96DXeAAdLv3t1M= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.1/go.mod h1:hGONorZkQCfR5DW6l2xdy7zC8vfO0r9pJlwyg6gmGeo= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -81,8 +82,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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= @@ -94,17 +95,12 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -119,7 +115,6 @@ github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu 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= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -129,46 +124,43 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg= github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= -github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= -github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= +github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -185,8 +177,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -204,22 +194,23 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt215EWcs98= +github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo= github.com/google/go-sev-guest v0.9.3 h1:GOJ+EipURdeWFl/YYdgcCxyPeMgQUWlI056iFkBD8UU= +github.com/google/go-sev-guest v0.9.3/go.mod h1:hc1R4R6f8+NcJwITs0L90fYWTsBpd1Ix+Gur15sqHDs= github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw= +github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE= github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= -github.com/google/go-tpm-tools v0.4.3 h1:L5dc34fttMIREoKRmnIJfv2NSZDSZ+RfBD+izN0EZoA= -github.com/google/go-tpm-tools v0.4.3/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= +github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= +github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -228,22 +219,20 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= -github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= -github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= @@ -255,101 +244,46 @@ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0S github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck= -github.com/hashicorp/vault/api v1.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE= -github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE= -github.com/hashicorp/vault/api/auth/approle v0.6.0 h1:ELfFFQlTM/e97WJKu1HvNFa7lQ3tlTwwzrR1NJE1V7Y= -github.com/hashicorp/vault/api/auth/approle v0.6.0/go.mod h1:CCoIl1xBC3lAWpd1HV+0ovk76Z8b8Mdepyk21h3pGk0= -github.com/hashicorp/vault/api/auth/kubernetes v0.6.0 h1:K8sKGhtTAqGKfzaaYvUSIOAqTOIn3Gk1EsCEAMzZHtM= -github.com/hashicorp/vault/api/auth/kubernetes v0.6.0/go.mod h1:Htwcjez5J9PwAHaZ1EYMBlgGq3/in5ajUV4+WCPihPE= +github.com/hashicorp/vault/api v1.14.0 h1:Ah3CFLixD5jmjusOgm8grfN9M0d+Y8fVR2SW0K6pJLU= +github.com/hashicorp/vault/api v1.14.0/go.mod h1:pV9YLxBGSz+cItFDd8Ii4G17waWOQ32zVjMWHe/cOqk= +github.com/hashicorp/vault/api/auth/approle v0.7.0 h1:R5IRVuFA5JSdG3UdGVcGysi0StrL1lPmyJnrawiV0Ss= +github.com/hashicorp/vault/api/auth/approle v0.7.0/go.mod h1:B+WaC6VR+aSXiUxykpaPUoFiiZAhic53tDLbGjWZmRA= +github.com/hashicorp/vault/api/auth/kubernetes v0.7.0 h1:pHCbeeyD6E5KmMMCc9vwwZZ5OVlM6yFayxFHWodiOUU= +github.com/hashicorp/vault/api/auth/kubernetes v0.7.0/go.mod h1:Eey0x0X2g+b2LYWgBrQFyf5W0fp+Y1HGrEckP8Q0wns= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -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.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= -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= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -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.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -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.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.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.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= -github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -372,9 +306,10 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR 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= -github.com/newrelic/go-agent/v3 v3.30.0 h1:ZXHCT/Cot4iIPwcegCZURuRQOsfmGA6wilW+S3bfBjY= -github.com/newrelic/go-agent/v3 v3.30.0/go.mod h1:9utrgxlSryNqRrTvII2XBL+0lpofXbqXApvVWPpbzUg= +github.com/newrelic/go-agent/v3 v3.33.1 h1:eWOtty43cyxrMKws4VNPdebgEB6ujFTf0yxPsgB0M80= +github.com/newrelic/go-agent/v3 v3.33.1/go.mod h1:SMdqPzE/ghkWdY0rYGSD7Clw2daK/XH6pUnVd4albg4= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= @@ -386,8 +321,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= @@ -395,29 +330,22 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM= @@ -426,8 +354,8 @@ github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= -github.com/smallstep/nosql v0.6.0 h1:ur7ysI8s9st0cMXnTvB8tA3+x5Eifmkb6hl4uqNV5jc= -github.com/smallstep/nosql v0.6.0/go.mod h1:jOXwLtockXORUPPZ2MCUcIkGR6w0cN1QGZniY9DITQA= +github.com/smallstep/nosql v0.7.0 h1:YiWC9ZAHcrLCrayfaF+QJUv16I2bZ7KdLC3RpJcnAnE= +github.com/smallstep/nosql v0.7.0/go.mod h1:H5VnKMCbeq9QA6SRY5iqPylfxLfYcLwvUff3onQ8+HU= github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= @@ -445,15 +373,13 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/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/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -464,16 +390,15 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 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/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= +github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= 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/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= -go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= +go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= @@ -484,54 +409,33 @@ go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= -go.step.sm/crypto v0.44.1 h1:8ouq8JEYXVxSymuVuX54Ilh5X2dqyjgOGGXyPeXDzV8= -go.step.sm/crypto v0.44.1/go.mod h1:hKl+QUIS4oJFRwQBRcVPz8NOYhRaoOJmwXHd2Y3XTA0= -go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= -go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= -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= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.step.sm/crypto v0.50.0 h1:BqI9sEgocoHDLLHiZnFqdqXl5FjdMvOWKMm/fKL/lrw= +go.step.sm/crypto v0.50.0/go.mod h1:NCFMhLS6FJXQ9sD9PP282oHtsBWLrI6wXZY0eOkq7t8= +go.step.sm/linkedca v0.22.1 h1:GvprpH9P4Sv9U+eZ3bxDgRSSpW14cFDYpe1kS6yWLkw= +go.step.sm/linkedca v0.22.1/go.mod h1:dOKdF4HSn73YUEkfS5/FECngZmBtj2Il5DTKWXY4S6Y= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 h1:LGJsf5LRplCck6jUCH3dBL2dmycNruWNF5xugkSlfXw= -golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc= +golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -541,7 +445,6 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -549,36 +452,26 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-20190423024810-112230192c58/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -594,80 +487,61 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.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/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= -google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= +google.golang.org/api v0.189.0 h1:equMo30LypAkdkLMBqfeIqtyAnlyig1JSZArl4XPwdI= +google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto v0.0.0-20240722135656-d784300faade h1:lKFsS7wpngDgSCeFn7MoLy+wBDQZ1UQIJD4UNM1Qvkg= +google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade h1:oCRSWfwGXQsqlVdErcyTt4A93Y8fo0/9D4b1gnI++qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -677,16 +551,12 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -696,4 +566,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/internal/metrix/meter.go b/internal/metrix/meter.go index 334cf883..ec858308 100644 --- a/internal/metrix/meter.go +++ b/internal/metrix/meter.go @@ -91,12 +91,12 @@ func (m *Meter) SSHSigned(p provisioner.Interface, err error) { incrProvisionerCounter(m.ssh.signed, p, err) } -// SSHAuthorized implements [authority.Meter] for [Meter]. +// SSHWebhookAuthorized implements [authority.Meter] for [Meter]. func (m *Meter) SSHWebhookAuthorized(p provisioner.Interface, err error) { incrProvisionerCounter(m.ssh.webhookAuthorized, p, err) } -// SSHEnriched implements [authority.Meter] for [Meter]. +// SSHWebhookEnriched implements [authority.Meter] for [Meter]. func (m *Meter) SSHWebhookEnriched(p provisioner.Interface, err error) { incrProvisionerCounter(m.ssh.webhookEnriched, p, err) } @@ -116,12 +116,12 @@ func (m *Meter) X509Signed(p provisioner.Interface, err error) { incrProvisionerCounter(m.x509.signed, p, err) } -// X509Authorized implements [authority.Meter] for [Meter]. +// X509WebhookAuthorized implements [authority.Meter] for [Meter]. func (m *Meter) X509WebhookAuthorized(p provisioner.Interface, err error) { incrProvisionerCounter(m.x509.webhookAuthorized, p, err) } -// X509Enriched implements [authority.Meter] for [Meter]. +// X509WebhookEnriched implements [authority.Meter] for [Meter]. func (m *Meter) X509WebhookEnriched(p provisioner.Interface, err error) { incrProvisionerCounter(m.x509.webhookEnriched, p, err) } diff --git a/policy/engine.go b/policy/engine.go index 56457325..0f9af0de 100755 --- a/policy/engine.go +++ b/policy/engine.go @@ -270,7 +270,7 @@ func (e *NamePolicyEngine) IsSSHCertificateAllowed(cert *ssh.Certificate) error return e.validateNames(dnsNames, ips, emails, []*url.URL{}, principals) } -// splitPrincipals splits SSH certificate principals into DNS names, emails and usernames. +// splitSSHPrincipals splits SSH certificate principals into DNS names, emails and usernames. func splitSSHPrincipals(cert *ssh.Certificate) (dnsNames []string, ips []net.IP, emails, principals []string, err error) { dnsNames = []string{} ips = []net.IP{} diff --git a/scep/api/api.go b/scep/api/api.go index bab60302..3649bf66 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -97,7 +97,7 @@ func route(r api.Router, middleware func(next http.HandlerFunc) http.HandlerFunc func Get(w http.ResponseWriter, r *http.Request) { req, err := decodeRequest(r) if err != nil { - fail(w, fmt.Errorf("invalid scep get request: %w", err)) + fail(w, r, fmt.Errorf("invalid scep get request: %w", err)) return } @@ -116,18 +116,18 @@ func Get(w http.ResponseWriter, r *http.Request) { } if err != nil { - fail(w, fmt.Errorf("scep get request failed: %w", err)) + fail(w, r, fmt.Errorf("scep get request failed: %w", err)) return } - writeResponse(w, res) + writeResponse(w, r, res) } // Post handles all SCEP POST requests func Post(w http.ResponseWriter, r *http.Request) { req, err := decodeRequest(r) if err != nil { - fail(w, fmt.Errorf("invalid scep post request: %w", err)) + fail(w, r, fmt.Errorf("invalid scep post request: %w", err)) return } @@ -140,11 +140,11 @@ func Post(w http.ResponseWriter, r *http.Request) { } if err != nil { - fail(w, fmt.Errorf("scep post request failed: %w", err)) + fail(w, r, fmt.Errorf("scep post request failed: %w", err)) return } - writeResponse(w, res) + writeResponse(w, r, res) } func decodeRequest(r *http.Request) (request, error) { @@ -274,7 +274,7 @@ func lookupProvisioner(next http.HandlerFunc) http.HandlerFunc { name := chi.URLParam(r, "provisionerName") provisionerName, err := url.PathUnescape(name) if err != nil { - fail(w, fmt.Errorf("error url unescaping provisioner name '%s'", name)) + fail(w, r, fmt.Errorf("error url unescaping provisioner name '%s'", name)) return } @@ -282,13 +282,13 @@ func lookupProvisioner(next http.HandlerFunc) http.HandlerFunc { auth := authority.MustFromContext(ctx) p, err := auth.LoadProvisionerByName(provisionerName) if err != nil { - fail(w, err) + fail(w, r, err) return } prov, ok := p.(*provisioner.SCEP) if !ok { - fail(w, errors.New("provisioner must be of type SCEP")) + fail(w, r, errors.New("provisioner must be of type SCEP")) return } @@ -387,9 +387,10 @@ func PKIOperation(ctx context.Context, req request) (Response, error) { if msg.MessageType == smallscep.PKCSReq || msg.MessageType == smallscep.RenewalReq { if err := auth.ValidateChallenge(ctx, csr, challengePassword, transactionID); err != nil { if errors.Is(err, provisioner.ErrSCEPChallengeInvalid) { - return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, err) + return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, err.Error(), err) } - return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, errors.New("failed validating challenge password")) + scepErr := errors.New("failed validating challenge password") + return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, scepErr.Error(), scepErr) } } @@ -407,7 +408,7 @@ func PKIOperation(ctx context.Context, req request) (Response, error) { // TODO(hs): ignore this error case? It's not critical if the notification fails; but logging it might be good _ = notifyErr } - return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, fmt.Errorf("error when signing new certificate: %w", err)) + return createFailureResponse(ctx, csr, msg, smallscep.BadRequest, "internal server error; please see the certificate authority logs for more info", fmt.Errorf("error when signing new certificate: %w", err)) } if notifyErr := auth.NotifySuccess(ctx, csr, certRep.Certificate, transactionID); notifyErr != nil { @@ -429,9 +430,9 @@ func formatCapabilities(caps []string) []byte { } // writeResponse writes a SCEP response back to the SCEP client. -func writeResponse(w http.ResponseWriter, res Response) { +func writeResponse(w http.ResponseWriter, r *http.Request, res Response) { if res.Error != nil { - log.Error(w, res.Error) + log.Error(w, r, res.Error) } if res.Certificate != nil { @@ -442,15 +443,15 @@ func writeResponse(w http.ResponseWriter, res Response) { _, _ = w.Write(res.Data) } -func fail(w http.ResponseWriter, err error) { - log.Error(w, err) +func fail(w http.ResponseWriter, r *http.Request, err error) { + log.Error(w, r, err) http.Error(w, err.Error(), http.StatusInternalServerError) } -func createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info smallscep.FailInfo, failError error) (Response, error) { +func createFailureResponse(ctx context.Context, csr *x509.CertificateRequest, msg *scep.PKIMessage, info smallscep.FailInfo, infoText string, failError error) (Response, error) { auth := scep.MustFromContext(ctx) - certRepMsg, err := auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), failError.Error()) + certRepMsg, err := auth.CreateFailureResponse(ctx, csr, msg, scep.FailInfoName(info), infoText) if err != nil { return Response{}, err } diff --git a/scep/authority.go b/scep/authority.go index 8ed065fb..00c58d8d 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -308,7 +308,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m certChain, err := a.signAuth.SignWithContext(ctx, csr, opts, signOps...) if err != nil { - return nil, fmt.Errorf("error generating certificate for order: %w", err) + return nil, fmt.Errorf("error generating certificate: %w", err) } // take the issued certificate (only); https://tools.ietf.org/html/rfc8894#section-3.3.2 diff --git a/scep/options.go b/scep/options.go index 8bc30a61..d173a76c 100644 --- a/scep/options.go +++ b/scep/options.go @@ -37,19 +37,21 @@ func (o *Options) Validate() error { switch { case len(o.Intermediates) == 0: return errors.New("no intermediate certificate available for SCEP authority") - case o.Signer == nil: - return errors.New("no signer available for SCEP authority") case o.SignerCert == nil: return errors.New("no signer certificate available for SCEP authority") } - // check if the signer (intermediate CA) certificate has the same public key as - // the signer. According to the RFC it seems valid to have different keys for - // the intermediate and the CA signing new certificates, so this might change - // in the future. - signerPublicKey := o.Signer.Public().(comparablePublicKey) - if !signerPublicKey.Equal(o.SignerCert.PublicKey) { - return errors.New("mismatch between signer certificate and public key") + // the signer is optional, but if it's set, its public key must match the signer + // certificate public key. + if o.Signer != nil { + // check if the signer (intermediate CA) certificate has the same public key as + // the signer. According to the RFC it seems valid to have different keys for + // the intermediate and the CA signing new certificates, so this might change + // in the future. + signerPublicKey := o.Signer.Public().(comparablePublicKey) + if !signerPublicKey.Equal(o.SignerCert.PublicKey) { + return errors.New("mismatch between signer certificate and public key") + } } // decrypter can be nil in case a signing only key is used; validation complete. diff --git a/scep/scep.go b/scep/scep.go index b89ed0ac..da7e202e 100644 --- a/scep/scep.go +++ b/scep/scep.go @@ -29,7 +29,7 @@ var ( oidSCEPsenderNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 5} oidSCEPrecipientNonce = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 6} oidSCEPtransactionID = asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 9, 7} - oidSCEPfailInfoText = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 24} + oidSCEPfailInfoText = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 24, 1} //oidChallengePassword = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 7} ) diff --git a/templates/values.go b/templates/values.go index aa158a92..f02ba476 100644 --- a/templates/values.go +++ b/templates/values.go @@ -108,10 +108,10 @@ var DefaultSSHTemplateData = map[string]string{ {{- end }} {{- if or .User.GOOS "none" | eq "windows" }} UserKnownHostsFile "{{.User.StepPath}}\ssh\known_hosts" - ProxyCommand C:\Windows\System32\cmd.exe /c step ssh proxycommand{{- if .User.Context }} --context {{ .User.Context }}{{- end }}{{- if .User.Provisioner }} --provisioner {{ .User.Provisioner }}{{- end }} %r %h %p + ProxyCommand C:\Windows\System32\cmd.exe /c step ssh proxycommand{{- if .User.Context }} --context {{ .User.Context }}{{- end }}{{- if .User.Console}} --console {{- end }}{{- if .User.Provisioner }} --provisioner {{ .User.Provisioner }}{{- end }} %r %h %p {{- else }} UserKnownHostsFile "{{.User.StepPath}}/ssh/known_hosts" - ProxyCommand step ssh proxycommand{{- if .User.Context }} --context {{ .User.Context }}{{- end }}{{- if .User.Provisioner }} --provisioner {{ .User.Provisioner }}{{- end }} %r %h %p + ProxyCommand step ssh proxycommand{{- if .User.Context }} --context {{ .User.Context }}{{- end }}{{- if .User.Console}} --console {{- end }}{{- if .User.Provisioner }} --provisioner {{ .User.Provisioner }}{{- end }} %r %h %p {{- end }} `, diff --git a/test/integration/scep/common_test.go b/test/integration/scep/common_test.go new file mode 100644 index 00000000..edab7894 --- /dev/null +++ b/test/integration/scep/common_test.go @@ -0,0 +1,277 @@ +package sceptest + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "errors" + "fmt" + "io" + "math/big" + "net" + "net/http" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smallstep/pkcs7" + "github.com/smallstep/scep" + "go.step.sm/crypto/minica" + "go.step.sm/crypto/x509util" + + "github.com/smallstep/certificates/ca" + "github.com/smallstep/certificates/cas/apiv1" +) + +func newCAClient(t *testing.T, caURL, rootFilepath string) *ca.Client { + caClient, err := ca.NewClient( + caURL, + ca.WithRootFile(rootFilepath), + ) + require.NoError(t, err) + return caClient +} + +func requireHealthyCA(t *testing.T, caClient *ca.Client) { + t.Helper() + // Wait for CA + time.Sleep(time.Second) + + ctx := context.Background() + healthResponse, err := caClient.HealthWithContext(ctx) + require.NoError(t, err) + if assert.NotNil(t, healthResponse) { + require.Equal(t, "ok", healthResponse.Status) + } +} + +// reservePort "reserves" a TCP port by opening a listener on a random +// port and immediately closing it. The port can then be assumed to be +// available for running a server on. +func reservePort(t *testing.T) (host, port string) { + t.Helper() + l, err := net.Listen("tcp", ":0") + require.NoError(t, err) + + address := l.Addr().String() + err = l.Close() + require.NoError(t, err) + + host, port, err = net.SplitHostPort(address) + require.NoError(t, err) + + return +} + +type client struct { + caURL string + caCert *x509.Certificate + httpClient *http.Client +} + +func createSCEPClient(t *testing.T, caURL string, root *x509.Certificate) *client { + t.Helper() + trustedRoots := x509.NewCertPool() + trustedRoots.AddCert(root) + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSClientConfig = &tls.Config{ + RootCAs: trustedRoots, + } + httpClient := &http.Client{ + Transport: transport, + } + return &client{ + caURL: caURL, + httpClient: httpClient, + } +} + +func (c *client) getCACert(t *testing.T) error { + // return early if CA certificate already available + if c.caCert != nil { + return nil + } + + resp, err := c.httpClient.Get(fmt.Sprintf("%s?operation=GetCACert&message=test", c.caURL)) + if err != nil { + return fmt.Errorf("failed get request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed reading response body: %w", err) + } + + t.Log(string(body)) + + // SCEP CA/RA certificate selection. If there's only a single certificate, it will + // be used as the CA certificate at all times. If there's multiple, the first certificate + // is assumed to be the certificate of the recipient to encrypt messages to. + switch ct := resp.Header.Get("Content-Type"); ct { + case "application/x-x509-ca-cert": + cert, err := x509.ParseCertificate(body) + if err != nil { + return fmt.Errorf("failed parsing response body: %w", err) + } + if _, ok := cert.PublicKey.(*rsa.PublicKey); !ok { + return fmt.Errorf("certificate has unexpected public key type %T", cert.PublicKey) + } + c.caCert = cert + case "application/x-x509-ca-ra-cert": + certs, err := scep.CACerts(body) + if err != nil { + return fmt.Errorf("failed parsing response body: %w", err) + } + cert := certs[0] + if _, ok := cert.PublicKey.(*rsa.PublicKey); !ok { + return fmt.Errorf("certificate has unexpected public key type %T", cert.PublicKey) + } + c.caCert = cert + default: + return fmt.Errorf("unexpected content-type value %q", ct) + } + + return nil +} + +func (c *client) requestCertificate(t *testing.T, commonName string, sans []string) (*x509.Certificate, error) { + if err := c.getCACert(t); err != nil { + return nil, fmt.Errorf("failed getting CA certificate: %w", err) + } + + signer, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, fmt.Errorf("failed creating SCEP private key: %w", err) + } + + csr, err := x509util.CreateCertificateRequest(commonName, sans, signer) + if err != nil { + return nil, fmt.Errorf("failed creating CSR: %w", err) + } + + tmpl := &x509.Certificate{ + Subject: csr.Subject, + PublicKey: signer.Public(), + SerialNumber: big.NewInt(1), + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(1 * time.Hour), + DNSNames: csr.DNSNames, + IPAddresses: csr.IPAddresses, + EmailAddresses: csr.EmailAddresses, + URIs: csr.URIs, + } + + selfSigned, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, signer.Public(), signer) + if err != nil { + return nil, fmt.Errorf("failed creating self signed certificate: %w", err) + } + selfSignedCertificate, err := x509.ParseCertificate(selfSigned) + if err != nil { + return nil, fmt.Errorf("failed parsing self signed certificate: %w", err) + } + + msgTmpl := &scep.PKIMessage{ + TransactionID: "test-1", + MessageType: scep.PKCSReq, + SenderNonce: []byte("test-nonce-1"), + Recipients: []*x509.Certificate{c.caCert}, + SignerCert: selfSignedCertificate, + SignerKey: signer, + } + + msg, err := scep.NewCSRRequest(csr, msgTmpl) + if err != nil { + return nil, fmt.Errorf("failed creating SCEP PKCSReq message: %w", err) + } + + t.Log(string(msg.Raw)) + + u, err := url.Parse(c.caURL) + if err != nil { + return nil, fmt.Errorf("failed parsing CA URL: %w", err) + } + + opURL := u.ResolveReference(&url.URL{RawQuery: fmt.Sprintf("operation=PKIOperation&message=%s", url.QueryEscape(base64.StdEncoding.EncodeToString(msg.Raw)))}) + resp, err := c.httpClient.Get(opURL.String()) + if err != nil { + return nil, fmt.Errorf("failed get request: %w", err) + } + defer resp.Body.Close() + + if ct := resp.Header.Get("Content-Type"); ct != "application/x-pki-message" { + return nil, fmt.Errorf("received unexpected content type %q", ct) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed reading response body: %w", err) + } + + t.Log(string(body)) + + signedData, err := pkcs7.Parse(body) + if err != nil { + return nil, fmt.Errorf("failed parsing response body: %w", err) + } + + // TODO: verify the signature? + + p7, err := pkcs7.Parse(signedData.Content) + if err != nil { + return nil, fmt.Errorf("failed decrypting inner p7: %w", err) + } + + content, err := p7.Decrypt(selfSignedCertificate, signer) + if err != nil { + return nil, fmt.Errorf("failed decrypting response: %w", err) + } + + p7, err = pkcs7.Parse(content) + if err != nil { + return nil, fmt.Errorf("failed parsing p7 content: %w", err) + } + + cert := p7.Certificates[0] + + return cert, nil +} + +type testCAS struct { + ca *minica.CA +} + +func (c *testCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { + cert, err := c.ca.SignCSR(req.CSR) + if err != nil { + return nil, fmt.Errorf("failed signing CSR: %w", err) + } + + return &apiv1.CreateCertificateResponse{ + Certificate: cert, + CertificateChain: []*x509.Certificate{cert, c.ca.Intermediate}, + }, nil +} +func (c *testCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { + return nil, errors.New("not implemented") +} + +func (c *testCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { + return nil, errors.New("not implemented") +} + +func (c *testCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityRequest) (*apiv1.GetCertificateAuthorityResponse, error) { + return &apiv1.GetCertificateAuthorityResponse{ + RootCertificate: c.ca.Root, + IntermediateCertificates: []*x509.Certificate{c.ca.Intermediate}, + }, nil +} + +var _ apiv1.CertificateAuthorityService = (*testCAS)(nil) +var _ apiv1.CertificateAuthorityGetter = (*testCAS)(nil) diff --git a/test/integration/scep/decrypter_cas_test.go b/test/integration/scep/decrypter_cas_test.go new file mode 100644 index 00000000..f19a2c91 --- /dev/null +++ b/test/integration/scep/decrypter_cas_test.go @@ -0,0 +1,149 @@ +package sceptest + +import ( + "context" + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "fmt" + "math/big" + "net" + "net/http" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/minica" + "go.step.sm/crypto/pemutil" + + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca" + "github.com/smallstep/certificates/cas/apiv1" +) + +func TestIssuesCertificateUsingSCEPWithDecrypterAndUpstreamCAS(t *testing.T) { + signer, err := keyutil.GenerateSigner("EC", "P-256", 0) + require.NoError(t, err) + + dir := t.TempDir() + m, err := minica.New(minica.WithName("Step E2E | SCEP Decrypter w/ Upstream CAS"), minica.WithGetSignerFunc(func() (crypto.Signer, error) { + return signer, nil + })) + require.NoError(t, err) + + rootFilepath := filepath.Join(dir, "root.crt") + _, err = pemutil.Serialize(m.Root, pemutil.WithFilename(rootFilepath)) + require.NoError(t, err) + + intermediateCertFilepath := filepath.Join(dir, "intermediate.crt") + _, err = pemutil.Serialize(m.Intermediate, pemutil.WithFilename(intermediateCertFilepath)) + require.NoError(t, err) + + intermediateKeyFilepath := filepath.Join(dir, "intermediate.key") + _, err = pemutil.Serialize(m.Signer, pemutil.WithFilename(intermediateKeyFilepath)) + require.NoError(t, err) + + decrypterKey, err := keyutil.GenerateKey("RSA", "", 2048) + require.NoError(t, err) + + decrypter, ok := decrypterKey.(crypto.Decrypter) + require.True(t, ok) + + decrypterCertifiate, err := m.Sign(&x509.Certificate{ + Subject: pkix.Name{CommonName: "decrypter"}, + PublicKey: decrypter.Public(), + SerialNumber: big.NewInt(1), + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(1 * time.Hour), + DNSNames: []string{"decrypter"}, + }) + require.NoError(t, err) + + b, err := pemutil.Serialize(decrypterCertifiate) + require.NoError(t, err) + decrypterCertificatePEMBytes := pem.EncodeToMemory(b) + + b, err = pemutil.Serialize(decrypter, pemutil.WithPassword([]byte("1234"))) + require.NoError(t, err) + decrypterKeyPEMBytes := pem.EncodeToMemory(b) + + // get a random address to listen on and connect to; currently no nicer way to get one before starting the server + // TODO(hs): find/implement a nicer way to expose the CA URL, similar to how e.g. httptest.Server exposes it? + host, port := reservePort(t) + + prov := &provisioner.SCEP{ + ID: "scep", + Name: "scep", + Type: "SCEP", + ForceCN: false, + ChallengePassword: "", + EncryptionAlgorithmIdentifier: 2, + MinimumPublicKeyLength: 2048, + Claims: &config.GlobalProvisionerClaims, + DecrypterCertificate: decrypterCertificatePEMBytes, + DecrypterKeyPEM: decrypterKeyPEMBytes, + DecrypterKeyPassword: "1234", + } + + err = prov.Init(provisioner.Config{}) + require.NoError(t, err) + + apiv1.Register("test-scep-cas", func(_ context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) { + return &testCAS{ + ca: m, + }, nil + }) + + cfg := &config.Config{ + Address: net.JoinHostPort(host, port), // reuse the address that was just "reserved" + DNSNames: []string{"127.0.0.1", "[::1]", "localhost"}, + AuthorityConfig: &config.AuthConfig{ + Options: &apiv1.Options{ + AuthorityID: "stepca-test-scep", + Type: "test-scep-cas", + CertificateAuthority: "test-cas", + }, + AuthorityID: "stepca-test-scep", + DeploymentType: "standalone-test", + Provisioners: provisioner.List{prov}, + }, + Logger: json.RawMessage(`{"format": "text"}`), + } + c, err := ca.New(cfg) + require.NoError(t, err) + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + err = c.Run() + require.ErrorIs(t, err, http.ErrServerClosed) + }() + + // instantiate a client for the CA running at the random address + caClient := newCAClient(t, fmt.Sprintf("https://localhost:%s", port), rootFilepath) + requireHealthyCA(t, caClient) + + scepClient := createSCEPClient(t, fmt.Sprintf("https://localhost:%s/scep/scep", port), m.Root) + cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"}) + assert.NoError(t, err) + require.NotNil(t, cert) + + assert.Equal(t, "test.localhost", cert.Subject.CommonName) + assert.Equal(t, "Step E2E | SCEP Decrypter w/ Upstream CAS Intermediate CA", cert.Issuer.CommonName) + + // done testing; stop and wait for the server to quit + err = c.Stop() + require.NoError(t, err) + + wg.Wait() +} diff --git a/test/integration/scep/decrypter_test.go b/test/integration/scep/decrypter_test.go new file mode 100644 index 00000000..f59ae8b1 --- /dev/null +++ b/test/integration/scep/decrypter_test.go @@ -0,0 +1,139 @@ +package sceptest + +import ( + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "fmt" + "math/big" + "net" + "net/http" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/minica" + "go.step.sm/crypto/pemutil" + + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca" +) + +func TestIssuesCertificateUsingSCEPWithDecrypter(t *testing.T) { + signer, err := keyutil.GenerateSigner("EC", "P-256", 0) + require.NoError(t, err) + + dir := t.TempDir() + m, err := minica.New(minica.WithName("Step E2E | SCEP Decrypter"), minica.WithGetSignerFunc(func() (crypto.Signer, error) { + return signer, nil + })) + require.NoError(t, err) + + rootFilepath := filepath.Join(dir, "root.crt") + _, err = pemutil.Serialize(m.Root, pemutil.WithFilename(rootFilepath)) + require.NoError(t, err) + + intermediateCertFilepath := filepath.Join(dir, "intermediate.crt") + _, err = pemutil.Serialize(m.Intermediate, pemutil.WithFilename(intermediateCertFilepath)) + require.NoError(t, err) + + intermediateKeyFilepath := filepath.Join(dir, "intermediate.key") + _, err = pemutil.Serialize(m.Signer, pemutil.WithFilename(intermediateKeyFilepath)) + require.NoError(t, err) + + decrypterKey, err := keyutil.GenerateKey("RSA", "", 2048) + require.NoError(t, err) + + decrypter, ok := decrypterKey.(crypto.Decrypter) + require.True(t, ok) + + decrypterCertifiate, err := m.Sign(&x509.Certificate{ + Subject: pkix.Name{CommonName: "decrypter"}, + PublicKey: decrypter.Public(), + SerialNumber: big.NewInt(1), + NotBefore: time.Now().Add(-1 * time.Hour), + NotAfter: time.Now().Add(1 * time.Hour), + DNSNames: []string{"decrypter"}, + }) + require.NoError(t, err) + + b, err := pemutil.Serialize(decrypterCertifiate) + require.NoError(t, err) + decrypterCertificatePEMBytes := pem.EncodeToMemory(b) + + b, err = pemutil.Serialize(decrypter, pemutil.WithPassword([]byte("1234"))) + require.NoError(t, err) + decrypterKeyPEMBytes := pem.EncodeToMemory(b) + + // get a random address to listen on and connect to; currently no nicer way to get one before starting the server + // TODO(hs): find/implement a nicer way to expose the CA URL, similar to how e.g. httptest.Server exposes it? + host, port := reservePort(t) + + prov := &provisioner.SCEP{ + ID: "scep", + Name: "scep", + Type: "SCEP", + ForceCN: false, + ChallengePassword: "", + EncryptionAlgorithmIdentifier: 2, + MinimumPublicKeyLength: 2048, + Claims: &config.GlobalProvisionerClaims, + DecrypterCertificate: decrypterCertificatePEMBytes, + DecrypterKeyPEM: decrypterKeyPEMBytes, + DecrypterKeyPassword: "1234", + } + + err = prov.Init(provisioner.Config{}) + require.NoError(t, err) + + cfg := &config.Config{ + Root: []string{rootFilepath}, + IntermediateCert: intermediateCertFilepath, + IntermediateKey: intermediateKeyFilepath, + Address: net.JoinHostPort(host, port), // reuse the address that was just "reserved" + DNSNames: []string{"127.0.0.1", "[::1]", "localhost"}, + AuthorityConfig: &config.AuthConfig{ + AuthorityID: "stepca-test-scep", + DeploymentType: "standalone-test", + Provisioners: provisioner.List{prov}, + }, + Logger: json.RawMessage(`{"format": "text"}`), + } + c, err := ca.New(cfg) + require.NoError(t, err) + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + err = c.Run() + require.ErrorIs(t, err, http.ErrServerClosed) + }() + + // instantiate a client for the CA running at the random address + caClient := newCAClient(t, fmt.Sprintf("https://localhost:%s", port), rootFilepath) + requireHealthyCA(t, caClient) + + scepClient := createSCEPClient(t, fmt.Sprintf("https://localhost:%s/scep/scep", port), m.Root) + cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"}) + assert.NoError(t, err) + require.NotNil(t, cert) + + assert.Equal(t, "test.localhost", cert.Subject.CommonName) + assert.Equal(t, "Step E2E | SCEP Decrypter Intermediate CA", cert.Issuer.CommonName) + + // done testing; stop and wait for the server to quit + err = c.Stop() + require.NoError(t, err) + + wg.Wait() +} diff --git a/test/integration/scep/regular_cas_test.go b/test/integration/scep/regular_cas_test.go new file mode 100644 index 00000000..ae5ebbfd --- /dev/null +++ b/test/integration/scep/regular_cas_test.go @@ -0,0 +1,116 @@ +package sceptest + +import ( + "context" + "crypto" + "encoding/json" + "fmt" + "net" + "net/http" + "path/filepath" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/minica" + "go.step.sm/crypto/pemutil" + + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca" + "github.com/smallstep/certificates/cas/apiv1" +) + +func TestFailsIssuingCertificateUsingRegularSCEPWithUpstreamCAS(t *testing.T) { + signer, err := keyutil.GenerateSigner("RSA", "", 2048) + require.NoError(t, err) + + dir := t.TempDir() + m, err := minica.New(minica.WithName("Step E2E | SCEP Regular w/ Upstream CAS"), minica.WithGetSignerFunc(func() (crypto.Signer, error) { + return signer, nil + })) + require.NoError(t, err) + + rootFilepath := filepath.Join(dir, "root.crt") + _, err = pemutil.Serialize(m.Root, pemutil.WithFilename(rootFilepath)) + require.NoError(t, err) + + intermediateCertFilepath := filepath.Join(dir, "intermediate.crt") + _, err = pemutil.Serialize(m.Intermediate, pemutil.WithFilename(intermediateCertFilepath)) + require.NoError(t, err) + + intermediateKeyFilepath := filepath.Join(dir, "intermediate.key") + _, err = pemutil.Serialize(m.Signer, pemutil.WithFilename(intermediateKeyFilepath)) + require.NoError(t, err) + + // get a random address to listen on and connect to; currently no nicer way to get one before starting the server + // TODO(hs): find/implement a nicer way to expose the CA URL, similar to how e.g. httptest.Server exposes it? + host, port := reservePort(t) + + prov := &provisioner.SCEP{ + ID: "scep", + Name: "scep", + Type: "SCEP", + ForceCN: false, + ChallengePassword: "", + EncryptionAlgorithmIdentifier: 2, + MinimumPublicKeyLength: 2048, + Claims: &config.GlobalProvisionerClaims, + } + + err = prov.Init(provisioner.Config{}) + require.NoError(t, err) + + apiv1.Register("test-scep-cas", func(_ context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) { + return &testCAS{ + ca: m, + }, nil + }) + + cfg := &config.Config{ + Address: net.JoinHostPort(host, port), // reuse the address that was just "reserved" + DNSNames: []string{"127.0.0.1", "[::1]", "localhost"}, + AuthorityConfig: &config.AuthConfig{ + Options: &apiv1.Options{ + AuthorityID: "stepca-test-scep", + Type: "test-scep-cas", + CertificateAuthority: "test-cas", + }, + AuthorityID: "stepca-test-scep", + DeploymentType: "standalone-test", + Provisioners: provisioner.List{prov}, + }, + Logger: json.RawMessage(`{"format": "text"}`), + } + c, err := ca.New(cfg) + require.NoError(t, err) + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + err = c.Run() + require.ErrorIs(t, err, http.ErrServerClosed) + }() + + // instantiate a client for the CA running at the random address + caClient := newCAClient(t, fmt.Sprintf("https://localhost:%s", port), rootFilepath) + requireHealthyCA(t, caClient) + + // issuance is expected to fail when an upstream CAS is configured, as the current + // CAS interfaces do not support providing a decrypter. + scepClient := createSCEPClient(t, fmt.Sprintf("https://localhost:%s/scep/scep", port), m.Root) + cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"}) + assert.Error(t, err) + assert.Nil(t, cert) + + // done testing; stop and wait for the server to quit + err = c.Stop() + require.NoError(t, err) + + wg.Wait() +} diff --git a/test/integration/scep/regular_test.go b/test/integration/scep/regular_test.go new file mode 100644 index 00000000..fc2d4d58 --- /dev/null +++ b/test/integration/scep/regular_test.go @@ -0,0 +1,107 @@ +package sceptest + +import ( + "crypto" + "encoding/json" + "fmt" + "net" + "net/http" + "path/filepath" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/minica" + "go.step.sm/crypto/pemutil" + + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca" +) + +func TestIssuesCertificateUsingRegularSCEPConfiguration(t *testing.T) { + signer, err := keyutil.GenerateSigner("RSA", "", 2048) + require.NoError(t, err) + + dir := t.TempDir() + m, err := minica.New(minica.WithName("Step E2E | SCEP Regular"), minica.WithGetSignerFunc(func() (crypto.Signer, error) { + return signer, nil + })) + require.NoError(t, err) + + rootFilepath := filepath.Join(dir, "root.crt") + _, err = pemutil.Serialize(m.Root, pemutil.WithFilename(rootFilepath)) + require.NoError(t, err) + + intermediateCertFilepath := filepath.Join(dir, "intermediate.crt") + _, err = pemutil.Serialize(m.Intermediate, pemutil.WithFilename(intermediateCertFilepath)) + require.NoError(t, err) + + intermediateKeyFilepath := filepath.Join(dir, "intermediate.key") + _, err = pemutil.Serialize(m.Signer, pemutil.WithFilename(intermediateKeyFilepath)) + require.NoError(t, err) + + // get a random address to listen on and connect to; currently no nicer way to get one before starting the server + // TODO(hs): find/implement a nicer way to expose the CA URL, similar to how e.g. httptest.Server exposes it? + host, port := reservePort(t) + + prov := &provisioner.SCEP{ + ID: "scep", + Name: "scep", + Type: "SCEP", + ForceCN: false, + ChallengePassword: "", + EncryptionAlgorithmIdentifier: 2, + MinimumPublicKeyLength: 2048, + Claims: &config.GlobalProvisionerClaims, + } + + err = prov.Init(provisioner.Config{}) + require.NoError(t, err) + + cfg := &config.Config{ + Root: []string{rootFilepath}, + IntermediateCert: intermediateCertFilepath, + IntermediateKey: intermediateKeyFilepath, + Address: net.JoinHostPort(host, port), // reuse the address that was just "reserved" + DNSNames: []string{"127.0.0.1", "[::1]", "localhost"}, + AuthorityConfig: &config.AuthConfig{ + AuthorityID: "stepca-test-scep", + DeploymentType: "standalone-test", + Provisioners: provisioner.List{prov}, + }, + Logger: json.RawMessage(`{"format": "text"}`), + } + c, err := ca.New(cfg) + require.NoError(t, err) + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer wg.Done() + err = c.Run() + require.ErrorIs(t, err, http.ErrServerClosed) + }() + + // instantiate a client for the CA running at the random address + caClient := newCAClient(t, fmt.Sprintf("https://localhost:%s", port), rootFilepath) + requireHealthyCA(t, caClient) + + scepClient := createSCEPClient(t, fmt.Sprintf("https://localhost:%s/scep/scep", port), m.Root) + cert, err := scepClient.requestCertificate(t, "test.localhost", []string{"test.localhost"}) + assert.NoError(t, err) + require.NotNil(t, cert) + + assert.Equal(t, "test.localhost", cert.Subject.CommonName) + assert.Equal(t, "Step E2E | SCEP Regular Intermediate CA", cert.Issuer.CommonName) + + // done testing; stop and wait for the server to quit + err = c.Stop() + require.NoError(t, err) + + wg.Wait() +}