Merge branch 'master' into fix-1637

This commit is contained in:
Mariano Cano
2024-07-23 11:37:00 -07:00
103 changed files with 3957 additions and 1307 deletions

17
.github/workflows/actionlint.yml vendored Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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:"-"`
}

View File

@@ -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)
}

View File

@@ -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-<test>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{

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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))
})
}
}

View File

@@ -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)

View File

@@ -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.

View File

@@ -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,
}

View File

@@ -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)

32
acme/db_test.go Normal file
View File

@@ -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)
}
})
}
}

View File

@@ -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())
}

View File

@@ -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
}

View File

@@ -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.
//

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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()
}
})
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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()

View File

@@ -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,

View File

@@ -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

View File

@@ -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())

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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))
})
}
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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())
}

View File

@@ -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]
}
}
}

View File

@@ -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`),
}
},
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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-----
-----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-----

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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")

View File

@@ -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)
})
}
}

View File

@@ -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 {

View File

@@ -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)
}
})
}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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),

View File

@@ -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,
},
},
},

View File

@@ -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
}

View File

@@ -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)
}
})
}
}

View File

@@ -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.
//

View File

@@ -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)
})
}
}

View File

@@ -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)
}
})

View File

@@ -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())

View File

@@ -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)

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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()
}))

View File

@@ -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 {

View File

@@ -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

View File

@@ -165,7 +165,8 @@ func (v *VaultCAS) GetCertificateAuthority(*apiv1.GetCertificateAuthorityRequest
}
return &apiv1.GetCertificateAuthorityResponse{
RootCertificate: cert.root,
RootCertificate: cert.root,
IntermediateCertificates: cert.intermediates,
}, nil
}

View File

@@ -83,6 +83,10 @@ Requires **--insecure** flag.`,
Usage: `the <port> 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 <file> 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 {

129
go.mod
View File

@@ -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
)

417
go.sum
View File

@@ -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=

View File

@@ -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)
}

View File

@@ -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{}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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}
)

View File

@@ -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 }}
`,

View File

@@ -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)

View File

@@ -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()
}

Some files were not shown because too many files have changed in this diff Show More