diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 8ca265e0..44908aae 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v1.6.0 + uses: dependabot/fetch-metadata@v2.0.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs diff --git a/.goreleaser.yml b/.goreleaser.yml index c00b26e8..41747192 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -98,7 +98,7 @@ signs: - cmd: cosign signature: "${artifact}.sig" certificate: "${artifact}.pem" - args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}"] + args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}", "--yes"] artifacts: all snapshot: @@ -180,7 +180,7 @@ release: Those were the changes on {{ .Tag }}! - Come join us on [Discord](https://discord.gg/X2RKGwEbV9) to ask questions, chat about PKI, or get a sneak peak at the freshest PKI memes. + Come join us on [Discord](https://discord.gg/X2RKGwEbV9) to ask questions, chat about PKI, or get a sneak peek at the freshest PKI memes. # You can disable this pipe in order to not upload any artifacts. # Defaults to false. diff --git a/CHANGELOG.md b/CHANGELOG.md index f978d601..1e9e2029 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Generation of first provisioner name on `step ca init` in (smallstep/certificates#1566) - Processing of SCEP Get PKIOperation requests in (smallstep/certificates#1570) -- Support for signing identity certificate during SSH sign by skipping URI validation in (smallstep/certificates#1572) +- Support for signing identity certificate during SSH sign by skipping URI validation in (smallstep/certificates#1572) - Dependency on `micromdm/scep` and `go.mozilla.org/pkcs7` to use Smallstep forks in (smallstep/certificates#1600) - Make the Common Name validator for JWK provisioners accept values from SANs too in (smallstep/certificates#1609) diff --git a/README.md b/README.md index 4505a7ef..6303ff0f 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,62 @@ -# Step Certificates +# step-ca -`step-ca` is an online certificate authority for secure, automated certificate management. It's the server counterpart to the [`step` CLI tool](https://github.com/smallstep/cli). +[![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest) +[![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates) +[![Build Status](https://github.com/smallstep/certificates/actions/workflows/test.yml/badge.svg)](https://github.com/smallstep/certificates) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![CLA assistant](https://cla-assistant.io/readme/badge/smallstep/certificates)](https://cla-assistant.io/smallstep/certificates) -You can use it to: -- Issue X.509 certificates for your internal infrastructure: - - HTTPS certificates that [work in browsers](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html) ([RFC5280](https://tools.ietf.org/html/rfc5280) and [CA/Browser Forum](https://cabforum.org/baseline-requirements-documents/) compliance) - - TLS certificates for VMs, containers, APIs, mobile clients, database connections, printers, wifi networks, toaster ovens... - - Client certificates to [enable mutual TLS (mTLS)](https://smallstep.com/hello-mtls) in your infra. mTLS is an optional feature in TLS where both client and server authenticate each other. Why add the complexity of a VPN when you can safely use mTLS over the public internet? +`step-ca` is an online certificate authority for secure, automated certificate management for DevOps. +It's the server counterpart to the [`step` CLI tool](https://github.com/smallstep/cli) for working with certificates and keys. +Both projects are maintained by [Smallstep Labs](https://smallstep.com). + +You can use `step-ca` to: +- Issue HTTPS server and client certificates that [work in browsers](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html) ([RFC5280](https://tools.ietf.org/html/rfc5280) and [CA/Browser Forum](https://cabforum.org/baseline-requirements-documents/) compliance) +- Issue TLS certificates for DevOps: VMs, containers, APIs, database connections, Kubernetes pods... - Issue SSH certificates: - - For people, in exchange for single sign-on ID tokens + - For people, in exchange for single sign-on identity tokens - For hosts, in exchange for cloud instance identity documents - Easily automate certificate management: - - It's an ACME v2 server - - It has a JSON API + - It's an [ACME server](https://smallstep.com/docs/step-ca/acme-basics/) that supports all [popular ACME challenge types](https://smallstep.com/docs/step-ca/acme-basics/#acme-challenge-types) - It comes with a [Go wrapper](./examples#user-content-basic-client-usage) - ... and there's a [command-line client](https://github.com/smallstep/cli) you can use in scripts! -Whatever your use case, `step-ca` is easy to use and hard to misuse, thanks to [safe, sane defaults](https://smallstep.com/docs/step-ca/certificate-authority-server-production#sane-cryptographic-defaults). - --- -**Don't want to run your own CA?** -To get up and running quickly, or as an alternative to running your own `step-ca` server, consider creating a [free hosted smallstep Certificate Manager authority](https://info.smallstep.com/certificate-manager-early-access-mvp/). +### Comparison with Smallstep's commercial product + +`step-ca` is optimized for a two-tier PKI serving common DevOps use cases. + +As you design your PKI, if you need any of the following, [consider our commerical CA](http://smallstep.com): +- Multiple certificate authorities +- Active revocation (CRL, OSCP) +- Turnkey high-volume, high availability CA +- An API for seamless IaC management of your PKI +- Integrated support for SCEP & NDES, for migrating from legacy Active Directory Certificate Services deployments +- Device identity — cross-platform device inventory and attestation using Secure Enclave & TPM 2.0 +- Highly automated PKI — managed certificate renewal, monitoring, TPM-based attested enrollment +- Seamless client deployments of EAP-TLS Wi-Fi, VPN, SSH, and browser certificates +- Jamf, Intune, or other MDM for root distribution and client enrollment +- Web Admin UI — history, issuance, and metrics +- ACME External Account Binding (EAB) +- Deep integration with an identity provider +- Fine-grained, role-based access control +- FIPS-compliant software +- HSM-bound private keys + +See our [full feature comparison](https://smallstep.com/step-ca-vs-smallstep-certificate-manager/) for more. + +You can [start a free trial](https://smallstep.com/signup) or [set up a call with us](https://go.smallstep.com/request-demo) to learn more. --- **Questions? Find us in [Discussions](https://github.com/smallstep/certificates/discussions) or [Join our Discord](https://u.step.sm/discord).** [Website](https://smallstep.com/certificates) | -[Documentation](https://smallstep.com/docs) | +[Documentation](https://smallstep.com/docs/step-ca) | [Installation](https://smallstep.com/docs/step-ca/installation) | -[Getting Started](https://smallstep.com/docs/step-ca/getting-started) | [Contributor's Guide](./CONTRIBUTING.md) -[![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases/latest) -[![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates) -[![Build Status](https://github.com/smallstep/certificates/actions/workflows/test.yml/badge.svg)](https://github.com/smallstep/certificates) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![CLA assistant](https://cla-assistant.io/readme/badge/smallstep/certificates)](https://cla-assistant.io/smallstep/certificates) - -[![GitHub stars](https://img.shields.io/github/stars/smallstep/certificates.svg?style=social)](https://github.com/smallstep/certificates/stargazers) -[![Twitter followers](https://img.shields.io/twitter/follow/smallsteplabs.svg?label=Follow&style=social)](https://twitter.com/intent/follow?screen_name=smallsteplabs) - -![star us](https://github.com/smallstep/certificates/raw/master/docs/images/star.gif) - ## Features ### 🦾 A fast, stable, flexible private CA @@ -52,7 +65,6 @@ Setting up a *public key infrastructure* (PKI) is out of reach for many small te - Choose key types (RSA, ECDSA, EdDSA) and lifetimes to suit your needs - [Short-lived certificates](https://smallstep.com/blog/passive-revocation.html) with automated enrollment, renewal, and passive revocation -- Capable of high availability (HA) deployment using [root federation](https://smallstep.com/blog/step-v0.8.3-federation-root-rotation.html) and/or multiple intermediaries - Can operate as [an online intermediate CA for an existing root CA](https://smallstep.com/docs/tutorials/intermediate-ca-new-ca) - [Badger, BoltDB, Postgres, and MySQL database backends](https://smallstep.com/docs/step-ca/configuration#databases) @@ -127,5 +139,5 @@ and visiting http://localhost:8080. ## Feedback? -* Tell us what you like and don't like about managing your PKI - we're eager to help solve problems in this space. -* Tell us about a feature you'd like to see! [Add a feature request Issue](https://github.com/smallstep/certificates/issues/new?assignees=&labels=enhancement%2C+needs+triage&template=enhancement.md&title=), [ask on Discussions](https://github.com/smallstep/certificates/discussions), or hit us up on [Twitter](https://twitter.com/smallsteplabs). +* Tell us what you like and don't like about managing your PKI - we're eager to help solve problems in this space. [Join our Discord](https://u.step.sm/discord) or [GitHub Discussions](https://github.com/smallstep/certificates/discussions) +* Tell us about a feature you'd like to see! [Request a Feature](https://github.com/smallstep/certificates/issues/new?assignees=&labels=enhancement%2C+needs+triage&template=enhancement.md&title=) diff --git a/acme/api/middleware.go b/acme/api/middleware.go index c3e1458e..afccca70 100644 --- a/acme/api/middleware.go +++ b/acme/api/middleware.go @@ -147,10 +147,10 @@ func validateJWS(next nextHTTP) nextHTTP { sig := jws.Signatures[0] uh := sig.Unprotected - if len(uh.KeyID) > 0 || + if uh.KeyID != "" || uh.JSONWebKey != nil || - len(uh.Algorithm) > 0 || - len(uh.Nonce) > 0 || + uh.Algorithm != "" || + uh.Nonce != "" || len(uh.ExtraHeaders) > 0 { render.Error(w, acme.NewError(acme.ErrorMalformedType, "unprotected header must not be used")) return @@ -199,7 +199,7 @@ func validateJWS(next nextHTTP) nextHTTP { return } - if hdr.JSONWebKey != nil && len(hdr.KeyID) > 0 { + if hdr.JSONWebKey != nil && hdr.KeyID != "" { render.Error(w, acme.NewError(acme.ErrorMalformedType, "jwk and kid are mutually exclusive")) return } diff --git a/acme/api/revoke_test.go b/acme/api/revoke_test.go index b01aff57..85b9a032 100644 --- a/acme/api/revoke_test.go +++ b/acme/api/revoke_test.go @@ -281,7 +281,7 @@ type mockCA struct { MockAreSANsallowed func(ctx context.Context, sans []string) error } -func (m *mockCA) Sign(*x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error) { +func (m *mockCA) SignWithContext(context.Context, *x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error) { return nil, nil } diff --git a/acme/challenge.go b/acme/challenge.go index b8294ef0..995981ab 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -528,6 +528,7 @@ type coseAlgorithmIdentifier int32 const ( coseAlgES256 coseAlgorithmIdentifier = -7 coseAlgRS256 coseAlgorithmIdentifier = -257 + coseAlgRS1 coseAlgorithmIdentifier = -65535 // deprecated, but (still) often used in TPMs ) func doTPMAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *attestationObject) (*tpmAttestationData, error) { @@ -652,15 +653,16 @@ func doTPMAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, return nil, NewDetailedError(ErrorBadAttestationStatementType, "invalid alg in attestation statement") } - // only RS256 and ES256 are allowed - coseAlg := coseAlgorithmIdentifier(alg) - if coseAlg != coseAlgRS256 && coseAlg != coseAlgES256 { + var hash crypto.Hash + switch coseAlgorithmIdentifier(alg) { + case coseAlgRS256, coseAlgES256: + hash = crypto.SHA256 + case coseAlgRS1: + hash = crypto.SHA1 + default: return nil, NewDetailedError(ErrorBadAttestationStatementType, "invalid alg %d in attestation statement", alg) } - // set the hash algorithm to use to SHA256 - hash := crypto.SHA256 - // recreate the generated key certification parameter values and verify // the attested key using the public key of the AK. certificationParameters := &attest.CertificationParameters{ diff --git a/acme/client.go b/acme/client.go index 51560cb8..8f506ef9 100644 --- a/acme/client.go +++ b/acme/client.go @@ -55,6 +55,7 @@ func NewClient() Client { http: &http.Client{ Timeout: 30 * time.Second, Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ //nolint:gosec // used on tls-alpn-01 challenge InsecureSkipVerify: true, // lgtm[go/disabled-certificate-check] diff --git a/acme/common.go b/acme/common.go index 7cce25fd..e86b23e9 100644 --- a/acme/common.go +++ b/acme/common.go @@ -21,7 +21,7 @@ var clock Clock // CertificateAuthority is the interface implemented by a CA authority. type CertificateAuthority interface { - Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) AreSANsAllowed(ctx context.Context, sans []string) error IsRevoked(sn string) (bool, error) Revoke(context.Context, *authority.RevokeOptions) error diff --git a/acme/order.go b/acme/order.go index 8dfcf97a..5a86c2c8 100644 --- a/acme/order.go +++ b/acme/order.go @@ -263,7 +263,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques signOps = append(signOps, extraOptions...) // Sign a new certificate. - certChain, err := auth.Sign(csr, provisioner.SignOptions{ + certChain, err := auth.SignWithContext(ctx, csr, provisioner.SignOptions{ NotBefore: provisioner.NewTimeDuration(o.NotBefore), NotAfter: provisioner.NewTimeDuration(o.NotAfter), }, signOps...) diff --git a/acme/order_test.go b/acme/order_test.go index 2851bb19..07372af0 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -271,16 +271,16 @@ func TestOrder_UpdateStatus(t *testing.T) { } type mockSignAuth struct { - sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + signWithContext func(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) areSANsAllowed func(ctx context.Context, sans []string) error loadProvisionerByName func(string) (provisioner.Interface, error) ret1, ret2 interface{} err error } -func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { - if m.sign != nil { - return m.sign(csr, signOpts, extraOpts...) +func (m *mockSignAuth) SignWithContext(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + if m.signWithContext != nil { + return m.signWithContext(ctx, csr, signOpts, extraOpts...) } else if m.err != nil { return nil, m.err } @@ -578,7 +578,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return nil, errors.New("force") }, @@ -628,7 +628,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, @@ -685,7 +685,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, @@ -770,7 +770,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{leaf, inter, root}, nil }, @@ -863,7 +863,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{leaf, inter, root}, nil }, @@ -973,7 +973,7 @@ func TestOrder_Finalize(t *testing.T) { // using the mocking functions as a wrapper for actual test helpers generated per test case or per // function that's tested. ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{leaf, inter, root}, nil }, @@ -1044,7 +1044,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, @@ -1108,7 +1108,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, @@ -1175,7 +1175,7 @@ func TestOrder_Finalize(t *testing.T) { }, }, ca: &mockSignAuth{ - sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(_ context.Context, _csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { assert.Equals(t, _csr, csr) return []*x509.Certificate{foo, bar, baz}, nil }, diff --git a/api/api.go b/api/api.go index 7cf44a11..6916983b 100644 --- a/api/api.go +++ b/api/api.go @@ -42,7 +42,7 @@ type Authority interface { AuthorizeRenewToken(ctx context.Context, ott string) (*x509.Certificate, error) GetTLSOptions() *config.TLSOptions Root(shasum string) (*x509.Certificate, error) - Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) Renew(peer *x509.Certificate) ([]*x509.Certificate, error) RenewContext(ctx context.Context, peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) @@ -54,7 +54,7 @@ type Authority interface { GetRoots() ([]*x509.Certificate, error) GetFederation() ([]*x509.Certificate, error) Version() authority.Version - GetCertificateRevocationList() ([]byte, error) + GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error) } // mustAuthority will be replaced on unit tests. @@ -565,7 +565,7 @@ func LogSSHCertificate(w http.ResponseWriter, cert *ssh.Certificate) { func ParseCursor(r *http.Request) (cursor string, limit int, err error) { q := r.URL.Query() cursor = q.Get("cursor") - if v := q.Get("limit"); len(v) > 0 { + if v := q.Get("limit"); v != "" { limit, err = strconv.Atoi(v) if err != nil { return "", 0, errs.BadRequestErr(err, "limit '%s' is not an integer", v) diff --git a/api/api_test.go b/api/api_test.go index b3c01816..8090c6d4 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -189,7 +189,7 @@ type mockAuthority struct { authorizeRenewToken func(ctx context.Context, ott string) (*x509.Certificate, error) getTLSOptions func() *authority.TLSOptions root func(shasum string) (*x509.Certificate, error) - sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + signWithContext func(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) renew func(cert *x509.Certificate) ([]*x509.Certificate, error) rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) renewContext func(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) @@ -200,7 +200,7 @@ type mockAuthority struct { getEncryptedKey func(kid string) (string, error) getRoots func() ([]*x509.Certificate, error) getFederation func() ([]*x509.Certificate, error) - getCRL func() ([]byte, error) + getCRL func() (*authority.CertificateRevocationListInfo, error) signSSH func(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error) @@ -214,12 +214,12 @@ type mockAuthority struct { version func() authority.Version } -func (m *mockAuthority) GetCertificateRevocationList() ([]byte, error) { +func (m *mockAuthority) GetCertificateRevocationList() (*authority.CertificateRevocationListInfo, error) { if m.getCRL != nil { return m.getCRL() } - return m.ret1.([]byte), m.err + return m.ret1.(*authority.CertificateRevocationListInfo), m.err } // TODO: remove once Authorize is deprecated. @@ -251,9 +251,9 @@ func (m *mockAuthority) Root(shasum string) (*x509.Certificate, error) { return m.ret1.(*x509.Certificate), m.err } -func (m *mockAuthority) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { - if m.sign != nil { - return m.sign(cr, opts, signOpts...) +func (m *mockAuthority) SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + if m.signWithContext != nil { + return m.signWithContext(ctx, cr, opts, signOpts...) } return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err } @@ -789,45 +789,6 @@ func (m *mockProvisioner) AuthorizeSSHRekey(ctx context.Context, token string) ( return m.ret1.(*ssh.Certificate), m.ret2.([]provisioner.SignOption), m.err } -func Test_CRLGeneration(t *testing.T) { - tests := []struct { - name string - err error - statusCode int - expected []byte - }{ - {"empty", nil, http.StatusOK, nil}, - } - - chiCtx := chi.NewRouteContext() - req := httptest.NewRequest("GET", "http://example.com/crl", http.NoBody) - req = req.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)) - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockMustAuthority(t, &mockAuthority{ret1: tt.expected, err: tt.err}) - w := httptest.NewRecorder() - CRL(w, req) - res := w.Result() - - if res.StatusCode != tt.statusCode { - t.Errorf("caHandler.CRL StatusCode = %d, wants %d", res.StatusCode, tt.statusCode) - } - - body, err := io.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Errorf("caHandler.Root unexpected error = %v", err) - } - if tt.statusCode == 200 { - if !bytes.Equal(bytes.TrimSpace(body), tt.expected) { - t.Errorf("caHandler.Root CRL = %s, wants %s", body, tt.expected) - } - } - }) - } -} - func Test_caHandler_Route(t *testing.T) { type fields struct { Authority Authority @@ -923,16 +884,12 @@ func Test_Sign(t *testing.T) { CsrPEM: CertificateRequest{csr}, OTT: "foobarzar", }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) invalid, err := json.Marshal(SignRequest{ CsrPEM: CertificateRequest{csr}, OTT: "", }) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) expected1 := []byte(`{"crt":"` + strings.ReplaceAll(certPEM, "\n", `\n`) + `\n","ca":"` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n","certChain":["` + strings.ReplaceAll(certPEM, "\n", `\n`) + `\n","` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n"]}`) expected2 := []byte(`{"crt":"` + strings.ReplaceAll(stepCertPEM, "\n", `\n`) + `\n","ca":"` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n","certChain":["` + strings.ReplaceAll(stepCertPEM, "\n", `\n`) + `\n","` + strings.ReplaceAll(rootPEM, "\n", `\n`) + `\n"]}`) diff --git a/api/crl.go b/api/crl.go index 6386f34a..c10d08ca 100644 --- a/api/crl.go +++ b/api/crl.go @@ -3,18 +3,32 @@ package api import ( "encoding/pem" "net/http" + "time" "github.com/smallstep/certificates/api/render" + "github.com/smallstep/certificates/errs" ) // CRL is an HTTP handler that returns the current CRL in DER or PEM format func CRL(w http.ResponseWriter, r *http.Request) { - crlBytes, err := mustAuthority(r.Context()).GetCertificateRevocationList() + crlInfo, err := mustAuthority(r.Context()).GetCertificateRevocationList() if err != nil { render.Error(w, err) return } + if crlInfo == nil { + render.Error(w, errs.New(http.StatusNotFound, "no CRL available")) + return + } + + expires := crlInfo.ExpiresAt + if expires.IsZero() { + expires = time.Now() + } + + w.Header().Add("Expires", expires.Format(time.RFC1123)) + _, formatAsPEM := r.URL.Query()["pem"] if formatAsPEM { w.Header().Add("Content-Type", "application/x-pem-file") @@ -22,11 +36,11 @@ func CRL(w http.ResponseWriter, r *http.Request) { _ = pem.Encode(w, &pem.Block{ Type: "X509 CRL", - Bytes: crlBytes, + Bytes: crlInfo.Data, }) } else { w.Header().Add("Content-Type", "application/pkix-crl") w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"") - w.Write(crlBytes) + w.Write(crlInfo.Data) } } diff --git a/api/crl_test.go b/api/crl_test.go new file mode 100644 index 00000000..5b194721 --- /dev/null +++ b/api/crl_test.go @@ -0,0 +1,93 @@ +package api + +import ( + "bytes" + "context" + "encoding/pem" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/go-chi/chi/v5" + "github.com/pkg/errors" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/errs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_CRL(t *testing.T) { + data := []byte{1, 2, 3, 4} + pemData := pem.EncodeToMemory(&pem.Block{ + Type: "X509 CRL", + Bytes: data, + }) + pemData = bytes.TrimSpace(pemData) + emptyPEMData := pem.EncodeToMemory(&pem.Block{ + Type: "X509 CRL", + Bytes: nil, + }) + emptyPEMData = bytes.TrimSpace(emptyPEMData) + tests := []struct { + name string + url string + err error + statusCode int + crlInfo *authority.CertificateRevocationListInfo + expectedBody []byte + expectedHeaders http.Header + expectedErrorJSON string + }{ + {"ok", "http://example.com/crl", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: data}, data, http.Header{"Content-Type": []string{"application/pkix-crl"}, "Content-Disposition": []string{`attachment; filename="crl.der"`}}, ""}, + {"ok/pem", "http://example.com/crl?pem=true", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: data}, pemData, http.Header{"Content-Type": []string{"application/x-pem-file"}, "Content-Disposition": []string{`attachment; filename="crl.pem"`}}, ""}, + {"ok/empty", "http://example.com/crl", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: nil}, nil, http.Header{"Content-Type": []string{"application/pkix-crl"}, "Content-Disposition": []string{`attachment; filename="crl.der"`}}, ""}, + {"ok/empty-pem", "http://example.com/crl?pem=true", nil, http.StatusOK, &authority.CertificateRevocationListInfo{Data: nil}, emptyPEMData, http.Header{"Content-Type": []string{"application/x-pem-file"}, "Content-Disposition": []string{`attachment; filename="crl.pem"`}}, ""}, + {"fail/internal", "http://example.com/crl", errs.Wrap(http.StatusInternalServerError, errors.New("failure"), "authority.GetCertificateRevocationList"), http.StatusInternalServerError, nil, nil, http.Header{}, `{"status":500,"message":"The certificate authority encountered an Internal Server Error. Please see the certificate authority logs for more info."}`}, + {"fail/nil", "http://example.com/crl", nil, http.StatusNotFound, nil, nil, http.Header{}, `{"status":404,"message":"no CRL available"}`}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockMustAuthority(t, &mockAuthority{ret1: tt.crlInfo, err: tt.err}) + + chiCtx := chi.NewRouteContext() + req := httptest.NewRequest("GET", tt.url, http.NoBody) + req = req.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)) + w := httptest.NewRecorder() + CRL(w, req) + res := w.Result() + + assert.Equal(t, tt.statusCode, res.StatusCode) + + body, err := io.ReadAll(res.Body) + res.Body.Close() + require.NoError(t, err) + + if tt.statusCode >= 300 { + assert.JSONEq(t, tt.expectedErrorJSON, string(bytes.TrimSpace(body))) + return + } + + // check expected header values + for _, h := range []string{"content-type", "content-disposition"} { + v := tt.expectedHeaders.Get(h) + require.NotEmpty(t, v) + + actual := res.Header.Get(h) + assert.Equal(t, v, actual) + } + + // check expires header value + assert.NotEmpty(t, res.Header.Get("expires")) + t1, err := time.Parse(time.RFC1123, res.Header.Get("expires")) + if assert.NoError(t, err) { + assert.False(t, t1.IsZero()) + } + + // check body contents + assert.Equal(t, tt.expectedBody, bytes.TrimSpace(body)) + }) + } +} diff --git a/api/revoke.go b/api/revoke.go index 4221696a..dc639d58 100644 --- a/api/revoke.go +++ b/api/revoke.go @@ -78,7 +78,7 @@ func Revoke(w http.ResponseWriter, r *http.Request) { // A token indicates that we are using the api via a provisioner token, // otherwise it is assumed that the certificate is revoking itself over mTLS. - if len(body.OTT) > 0 { + if body.OTT != "" { logOtt(w, body.OTT) if _, err := a.Authorize(ctx, body.OTT); err != nil { render.Error(w, errs.UnauthorizedErr(err)) diff --git a/api/sign.go b/api/sign.go index c0c83ce2..26b3c396 100644 --- a/api/sign.go +++ b/api/sign.go @@ -78,7 +78,7 @@ func Sign(w http.ResponseWriter, r *http.Request) { return } - certChain, err := a.Sign(body.CsrPEM.CertificateRequest, opts, signOpts...) + certChain, err := a.SignWithContext(ctx, body.CsrPEM.CertificateRequest, opts, signOpts...) if err != nil { render.Error(w, errs.ForbiddenErr(err, "error signing certificate")) return diff --git a/api/ssh.go b/api/ssh.go index 9d0bbc14..08294c71 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -330,7 +330,7 @@ func SSHSign(w http.ResponseWriter, r *http.Request) { NotAfter: time.Unix(int64(cert.ValidBefore), 0), }) - certChain, err := a.Sign(cr, provisioner.SignOptions{}, signOpts...) + certChain, err := a.SignWithContext(ctx, cr, provisioner.SignOptions{}, signOpts...) if err != nil { render.Error(w, errs.ForbiddenErr(err, "error signing identity certificate")) return diff --git a/api/ssh_test.go b/api/ssh_test.go index 57dd6775..2b90dc12 100644 --- a/api/ssh_test.go +++ b/api/ssh_test.go @@ -325,7 +325,7 @@ func Test_SSHSign(t *testing.T) { signSSHAddUser: func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error) { return tt.addUserCert, tt.addUserErr }, - sign: func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + signWithContext: func(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { return tt.tlsSignCerts, tt.tlsSignErr }, }) diff --git a/authority/admin/api/provisioner.go b/authority/admin/api/provisioner.go index d44e9e03..709399dd 100644 --- a/authority/admin/api/provisioner.go +++ b/authority/admin/api/provisioner.go @@ -38,7 +38,7 @@ func GetProvisioner(w http.ResponseWriter, r *http.Request) { auth := mustAuthority(ctx) db := admin.MustFromContext(ctx) - if len(id) > 0 { + if id != "" { if p, err = auth.LoadProvisionerByID(id); err != nil { render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id)) return @@ -116,7 +116,7 @@ func DeleteProvisioner(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") auth := mustAuthority(r.Context()) - if len(id) > 0 { + if id != "" { if p, err = auth.LoadProvisionerByID(id); err != nil { render.Error(w, admin.WrapErrorISE(err, "error loading provisioner %s", id)) return diff --git a/authority/admin/db/nosql/admin_test.go b/authority/admin/db/nosql/admin_test.go index 9961d7f5..a50fe58b 100644 --- a/authority/admin/db/nosql/admin_test.go +++ b/authority/admin/db/nosql/admin_test.go @@ -857,7 +857,7 @@ func TestDB_CreateAdmin(t *testing.T) { var _dba = new(dbAdmin) assert.FatalError(t, json.Unmarshal(nu, _dba)) - assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key)) + assert.True(t, _dba.ID != "" && _dba.ID == string(key)) assert.Equals(t, _dba.AuthorityID, adm.AuthorityId) assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId) assert.Equals(t, _dba.Subject, adm.Subject) @@ -890,7 +890,7 @@ func TestDB_CreateAdmin(t *testing.T) { var _dba = new(dbAdmin) assert.FatalError(t, json.Unmarshal(nu, _dba)) - assert.True(t, len(_dba.ID) > 0 && _dba.ID == string(key)) + assert.True(t, _dba.ID != "" && _dba.ID == string(key)) assert.Equals(t, _dba.AuthorityID, adm.AuthorityId) assert.Equals(t, _dba.ProvisionerID, adm.ProvisionerId) assert.Equals(t, _dba.Subject, adm.Subject) diff --git a/authority/admin/db/nosql/provisioner_test.go b/authority/admin/db/nosql/provisioner_test.go index 8aa58d49..73e0368d 100644 --- a/authority/admin/db/nosql/provisioner_test.go +++ b/authority/admin/db/nosql/provisioner_test.go @@ -906,7 +906,7 @@ func TestDB_CreateProvisioner(t *testing.T) { var _dbp = new(dbProvisioner) assert.FatalError(t, json.Unmarshal(nu, _dbp)) - assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) + assert.True(t, _dbp.ID != "" && _dbp.ID == string(key)) assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) assert.Equals(t, _dbp.Type, prov.Type) assert.Equals(t, _dbp.Name, prov.Name) @@ -944,7 +944,7 @@ func TestDB_CreateProvisioner(t *testing.T) { var _dbp = new(dbProvisioner) assert.FatalError(t, json.Unmarshal(nu, _dbp)) - assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) + assert.True(t, _dbp.ID != "" && _dbp.ID == string(key)) assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) assert.Equals(t, _dbp.Type, prov.Type) assert.Equals(t, _dbp.Name, prov.Name) @@ -1093,7 +1093,7 @@ func TestDB_UpdateProvisioner(t *testing.T) { var _dbp = new(dbProvisioner) assert.FatalError(t, json.Unmarshal(nu, _dbp)) - assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) + assert.True(t, _dbp.ID != "" && _dbp.ID == string(key)) assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) assert.Equals(t, _dbp.Type, prov.Type) assert.Equals(t, _dbp.Name, prov.Name) @@ -1188,7 +1188,7 @@ func TestDB_UpdateProvisioner(t *testing.T) { var _dbp = new(dbProvisioner) assert.FatalError(t, json.Unmarshal(nu, _dbp)) - assert.True(t, len(_dbp.ID) > 0 && _dbp.ID == string(key)) + assert.True(t, _dbp.ID != "" && _dbp.ID == string(key)) assert.Equals(t, _dbp.AuthorityID, prov.AuthorityId) assert.Equals(t, _dbp.Type, prov.Type) assert.Equals(t, _dbp.Name, prov.Name) diff --git a/authority/authority.go b/authority/authority.go index 95e00a45..c112bc25 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -104,6 +104,9 @@ type Authority struct { // If true, do not output initialization logs quietInit bool + + // Called whenever applicable, in order to instrument the authority. + meter Meter } // Info contains information about the authority. @@ -126,6 +129,7 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) { config: cfg, certificates: new(sync.Map), validateSCEP: true, + meter: noopMeter{}, } // Apply options. @@ -134,6 +138,9 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) { return nil, err } } + if a.keyManager != nil { + a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter} + } if !a.skipInit { // Initialize authority from options or configuration. @@ -151,6 +158,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) { a := &Authority{ config: &config.Config{}, certificates: new(sync.Map), + meter: noopMeter{}, } // Apply options. @@ -159,6 +167,9 @@ func NewEmbedded(opts ...Option) (*Authority, error) { return nil, err } } + if a.keyManager != nil { + a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter} + } // Validate required options switch { @@ -337,6 +348,8 @@ func (a *Authority) init() error { if err != nil { return err } + + a.keyManager = &instrumentedKeyManager{a.keyManager, a.meter} } // Initialize linkedca client if necessary. On a linked RA, the issuer diff --git a/authority/authority_test.go b/authority/authority_test.go index 45c7cd86..3787dab7 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -1,6 +1,7 @@ package authority import ( + "context" "crypto" "crypto/rand" "crypto/sha256" @@ -414,7 +415,7 @@ func TestNewEmbedded_Sign(t *testing.T) { csr, err := x509.ParseCertificateRequest(cr) assert.FatalError(t, err) - cert, err := a.Sign(csr, provisioner.SignOptions{}) + cert, err := a.SignWithContext(context.Background(), csr, provisioner.SignOptions{}) assert.FatalError(t, err) assert.Equals(t, []string{"foo.bar.zar"}, cert[0].DNSNames) assert.Equals(t, crt, cert[1]) diff --git a/authority/authorize.go b/authority/authorize.go index f14574a8..02147687 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -286,16 +286,16 @@ func (a *Authority) authorizeRevoke(ctx context.Context, token string) error { // extra extension cannot be found, authorize the renewal by default. // // TODO(mariano): should we authorize by default? -func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) error { +func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) (provisioner.Interface, error) { serial := cert.SerialNumber.String() var opts = []interface{}{errs.WithKeyVal("serialNumber", serial)} isRevoked, err := a.IsRevoked(serial) if err != nil { - return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) + return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) } if isRevoked { - return errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...) + return nil, errs.Unauthorized("authority.authorizeRenew: certificate has been revoked", opts...) } p, err := a.LoadProvisionerByCertificate(cert) if err != nil { @@ -305,13 +305,13 @@ func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) // returns the noop provisioner if this happens, and it allows // certificate renewals. if p, ok = a.provisioners.LoadByCertificate(cert); !ok { - return errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...) + return nil, errs.Unauthorized("authority.authorizeRenew: provisioner not found", opts...) } } if err := p.AuthorizeRenew(ctx, cert); err != nil { - return errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) + return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.authorizeRenew", opts...) } - return nil + return p, nil } // authorizeSSHCertificate returns an error if the given certificate is revoked. diff --git a/authority/authorize_test.go b/authority/authorize_test.go index 975ffc01..551fd95a 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -909,7 +909,7 @@ func TestAuthority_authorizeRenew(t *testing.T) { t.Run(name, func(t *testing.T) { tc := genTestCase(t) - err := tc.auth.authorizeRenew(context.Background(), tc.cert) + _, err := tc.auth.authorizeRenew(context.Background(), tc.cert) if err != nil { if assert.NotNil(t, tc.err) { var sc render.StatusCodedError @@ -1408,7 +1408,7 @@ func TestAuthority_AuthorizeRenewToken(t *testing.T) { } generateX5cToken := func(a *Authority, key crypto.Signer, claims jose.Claims, opts ...provisioner.SignOption) (string, *x509.Certificate) { - chain, err := a.Sign(csr, provisioner.SignOptions{}, opts...) + chain, err := a.SignWithContext(ctx, csr, provisioner.SignOptions{}, opts...) if err != nil { t.Fatal(err) } diff --git a/authority/config/config.go b/authority/config/config.go index ba581d8a..ea7ce35d 100644 --- a/authority/config/config.go +++ b/authority/config/config.go @@ -83,6 +83,7 @@ type Config struct { Templates *templates.Templates `json:"templates,omitempty"` CommonName string `json:"commonName,omitempty"` CRL *CRLConfig `json:"crl,omitempty"` + MetricsAddress string `json:"metricsAddress,omitempty"` SkipValidation bool `json:"-"` // Keeps record of the filename the Config is read from @@ -327,6 +328,12 @@ func (c *Config) Validate() error { return errors.Errorf("invalid address %s", c.Address) } + if addr := c.MetricsAddress; addr != "" { + if _, _, err := net.SplitHostPort(addr); err != nil { + return errors.Errorf("invalid metrics address %q", c.Address) + } + } + if c.TLS == nil { c.TLS = &DefaultTLSOptions } else { diff --git a/authority/internal/constraints/verify.go b/authority/internal/constraints/verify.go index 5d070f1e..eae5c8a5 100644 --- a/authority/internal/constraints/verify.go +++ b/authority/internal/constraints/verify.go @@ -203,7 +203,7 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) { // domainToReverseLabels converts a textual domain name like foo.example.com to // the list of labels in reverse order, e.g. ["com", "example", "foo"]. func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { - for len(domain) > 0 { + for domain != "" { if i := strings.LastIndexByte(domain, '.'); i == -1 { reverseLabels = append(reverseLabels, domain) domain = "" @@ -316,7 +316,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) { } else { // Atom ("." Atom)* NextChar: - for len(in) > 0 { + for in != "" { // atext from RFC 2822, Section 3.2.4 c := in[0] diff --git a/authority/meter.go b/authority/meter.go new file mode 100644 index 00000000..cccda22a --- /dev/null +++ b/authority/meter.go @@ -0,0 +1,87 @@ +package authority + +import ( + "crypto" + "io" + + "go.step.sm/crypto/kms" + kmsapi "go.step.sm/crypto/kms/apiv1" + + "github.com/smallstep/certificates/authority/provisioner" +) + +// Meter wraps the set of defined callbacks for metrics gatherers. +type Meter interface { + // X509Signed is called whenever an X509 certificate is signed. + X509Signed(provisioner.Interface, error) + + // X509Renewed is called whenever an X509 certificate is renewed. + X509Renewed(provisioner.Interface, error) + + // X509Rekeyed is called whenever an X509 certificate is rekeyed. + X509Rekeyed(provisioner.Interface, error) + + // X509WebhookAuthorized is called whenever an X509 authoring webhook is called. + X509WebhookAuthorized(provisioner.Interface, error) + + // X509WebhookEnriched is called whenever an X509 enriching webhook is called. + X509WebhookEnriched(provisioner.Interface, error) + + // SSHSigned is called whenever an SSH certificate is signed. + SSHSigned(provisioner.Interface, error) + + // SSHRenewed is called whenever an SSH certificate is renewed. + SSHRenewed(provisioner.Interface, error) + + // SSHRekeyed is called whenever an SSH certificate is rekeyed. + SSHRekeyed(provisioner.Interface, error) + + // SSHWebhookAuthorized is called whenever an SSH authoring webhook is called. + SSHWebhookAuthorized(provisioner.Interface, error) + + // SSHWebhookEnriched is called whenever an SSH enriching webhook is called. + SSHWebhookEnriched(provisioner.Interface, error) + + // KMSSigned is called per KMS signer signature. + KMSSigned(error) +} + +// noopMeter implements a noop [Meter]. +type noopMeter struct{} + +func (noopMeter) SSHRekeyed(provisioner.Interface, error) {} +func (noopMeter) SSHRenewed(provisioner.Interface, error) {} +func (noopMeter) SSHSigned(provisioner.Interface, error) {} +func (noopMeter) SSHWebhookAuthorized(provisioner.Interface, error) {} +func (noopMeter) SSHWebhookEnriched(provisioner.Interface, error) {} +func (noopMeter) X509Rekeyed(provisioner.Interface, error) {} +func (noopMeter) X509Renewed(provisioner.Interface, error) {} +func (noopMeter) X509Signed(provisioner.Interface, error) {} +func (noopMeter) X509WebhookAuthorized(provisioner.Interface, error) {} +func (noopMeter) X509WebhookEnriched(provisioner.Interface, error) {} +func (noopMeter) KMSSigned(error) {} + +type instrumentedKeyManager struct { + kms.KeyManager + meter Meter +} + +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} + } + + return +} + +type instrumentedKMSSigner struct { + crypto.Signer + meter Meter +} + +func (i *instrumentedKMSSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { + signature, err = i.Signer.Sign(rand, digest, opts) + i.meter.KMSSigned(err) + + return +} diff --git a/authority/options.go b/authority/options.go index 4fc5a20f..55c27321 100644 --- a/authority/options.go +++ b/authority/options.go @@ -167,6 +167,15 @@ func WithKeyManager(k kms.KeyManager) Option { } } +// WithX509CAService allows the consumer to provide an externally implemented +// API implementation of apiv1.CertificateAuthorityService +func WithX509CAService(svc casapi.CertificateAuthorityService) Option { + return func(a *Authority) error { + a.x509CAService = svc + return nil + } +} + // WithX509Signer defines the signer used to sign X509 certificates. func WithX509Signer(crt *x509.Certificate, s crypto.Signer) Option { return WithX509SignerChain([]*x509.Certificate{crt}, s) @@ -381,3 +390,16 @@ func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) { } return certs, nil } + +// WithMeter is an option that sets the authority's [Meter] to the provided one. +func WithMeter(m Meter) Option { + if m == nil { + m = noopMeter{} + } + + return func(a *Authority) (_ error) { + a.meter = m + + return + } +} diff --git a/authority/provisioner/aws_certificates.pem b/authority/provisioner/aws_certificates.pem index d9b5f639..994758b9 100644 --- a/authority/provisioner/aws_certificates.pem +++ b/authority/provisioner/aws_certificates.pem @@ -1,4 +1,5 @@ # 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" -----BEGIN CERTIFICATE----- @@ -244,4 +245,20 @@ Af8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUAA4GBACrKjWj460GUPZCGm3/z0dIz M2BPuH769wcOsqfFZcMKEysSFK91tVtUb1soFwH4/Lb/T0PqNrvtEwD1Nva5k0h2 xZhNNRmDuhOhW1K9wCcnHGRBwY5t4lYL6hNV6hcrqYwGMjTjcAjBG2yMgznSNFle Rwi/S3BFXISixNx9cILu +-----END CERTIFICATE----- + +# certificate for ca-west-1 +-----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----- \ No newline at end of file diff --git a/authority/provisioner/aws_test.go b/authority/provisioner/aws_test.go index 02be1ba9..f2485e93 100644 --- a/authority/provisioner/aws_test.go +++ b/authority/provisioner/aws_test.go @@ -896,5 +896,5 @@ func TestAWS_HardcodedCertificates(t *testing.T) { assert.True(t, cert.NotAfter.After(time.Now())) certs = append(certs, cert) } - assert.Len(t, 14, certs, "expected 14 certificates in aws_certificates.pem") + assert.Len(t, 15, certs, "expected 15 certificates in aws_certificates.pem") } diff --git a/authority/provisioner/collection.go b/authority/provisioner/collection.go index c483a50d..fbb730db 100644 --- a/authority/provisioner/collection.go +++ b/authority/provisioner/collection.go @@ -125,7 +125,7 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) } // Try with azp (OIDC) - if len(payload.AuthorizedParty) > 0 { + if payload.AuthorizedParty != "" { if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok { return p, ok } diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index 2f73c8e5..5105a881 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -92,7 +92,7 @@ func (p *JWK) GetType() Type { // GetEncryptedKey returns the base provisioner encrypted key if it's defined. func (p *JWK) GetEncryptedKey() (string, string, bool) { - return p.Key.KeyID, p.EncryptedKey, len(p.EncryptedKey) > 0 + return p.Key.KeyID, p.EncryptedKey, p.EncryptedKey != "" } // Init initializes and validates the fields of a JWK type. diff --git a/authority/provisioner/keystore.go b/authority/provisioner/keystore.go index e74a6b8a..aeaad6a0 100644 --- a/authority/provisioner/keystore.go +++ b/authority/provisioner/keystore.go @@ -105,7 +105,7 @@ func getKeysFromJWKsURI(uri string) (jose.JSONWebKeySet, time.Duration, error) { func getCacheAge(cacheControl string) time.Duration { age := defaultCacheAge - if len(cacheControl) > 0 { + if cacheControl != "" { match := maxAgeRegex.FindAllStringSubmatch(cacheControl, -1) if len(match) > 0 { if len(match[0]) == 2 { diff --git a/authority/provisioner/scep.go b/authority/provisioner/scep.go index a48d11cc..f4067bc5 100644 --- a/authority/provisioner/scep.go +++ b/authority/provisioner/scep.go @@ -304,7 +304,7 @@ func (s *SCEP) Init(config Config) (err error) { } } - if decryptionKeyURI := s.DecrypterKeyURI; len(decryptionKeyURI) > 0 { + if decryptionKeyURI := s.DecrypterKeyURI; decryptionKeyURI != "" { u, err := uri.Parse(s.DecrypterKeyURI) if err != nil { return fmt.Errorf("failed parsing decrypter key: %w", err) diff --git a/authority/provisioner/webhook.go b/authority/provisioner/webhook.go index 4b517bb6..05f972fe 100644 --- a/authority/provisioner/webhook.go +++ b/authority/provisioner/webhook.go @@ -15,6 +15,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/smallstep/certificates/middleware/requestid" "github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/webhook" "go.step.sm/linkedca" @@ -36,7 +37,7 @@ type WebhookController struct { // Enrich fetches data from remote servers and adds returned data to the // templateData -func (wc *WebhookController) Enrich(req *webhook.RequestBody) error { +func (wc *WebhookController) Enrich(ctx context.Context, req *webhook.RequestBody) error { if wc == nil { return nil } @@ -55,7 +56,11 @@ func (wc *WebhookController) Enrich(req *webhook.RequestBody) error { if !wc.isCertTypeOK(wh) { continue } - resp, err := wh.Do(wc.client, req, wc.TemplateData) + + whCtx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() //nolint:gocritic // every request canceled with its own timeout + + resp, err := wh.DoWithContext(whCtx, wc.client, req, wc.TemplateData) if err != nil { return err } @@ -68,7 +73,7 @@ func (wc *WebhookController) Enrich(req *webhook.RequestBody) error { } // Authorize checks that all remote servers allow the request -func (wc *WebhookController) Authorize(req *webhook.RequestBody) error { +func (wc *WebhookController) Authorize(ctx context.Context, req *webhook.RequestBody) error { if wc == nil { return nil } @@ -87,7 +92,11 @@ func (wc *WebhookController) Authorize(req *webhook.RequestBody) error { if !wc.isCertTypeOK(wh) { continue } - resp, err := wh.Do(wc.client, req, wc.TemplateData) + + whCtx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() //nolint:gocritic // every request canceled with its own timeout + + resp, err := wh.DoWithContext(whCtx, wc.client, req, wc.TemplateData) if err != nil { return err } @@ -123,13 +132,6 @@ type Webhook struct { } `json:"-"` } -func (w *Webhook) Do(client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - - return w.DoWithContext(ctx, client, reqBody, data) -} - func (w *Webhook) DoWithContext(ctx context.Context, client *http.Client, reqBody *webhook.RequestBody, data any) (*webhook.ResponseBody, error) { tmpl, err := template.New("url").Funcs(templates.StepFuncMap()).Parse(w.URL) if err != nil { @@ -169,6 +171,10 @@ retry: return nil, err } + if requestID, ok := requestid.FromContext(ctx); ok { + req.Header.Set("X-Request-Id", requestID) + } + secret, err := base64.StdEncoding.DecodeString(w.Secret) if err != nil { return nil, err diff --git a/authority/provisioner/webhook_test.go b/authority/provisioner/webhook_test.go index 9a2b62f0..75dd0793 100644 --- a/authority/provisioner/webhook_test.go +++ b/authority/provisioner/webhook_test.go @@ -1,6 +1,7 @@ package provisioner import ( + "context" "crypto/hmac" "crypto/sha256" "crypto/tls" @@ -8,18 +9,23 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "net/http" "net/http/httptest" "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - "github.com/pkg/errors" - "github.com/smallstep/assert" - "github.com/smallstep/certificates/webhook" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" "go.step.sm/linkedca" + + "github.com/smallstep/certificates/middleware/requestid" + "github.com/smallstep/certificates/webhook" ) func TestWebhookController_isCertTypeOK(t *testing.T) { @@ -92,19 +98,25 @@ func TestWebhookController_isCertTypeOK(t *testing.T) { } for name, test := range tests { t.Run(name, func(t *testing.T) { - assert.Equals(t, test.want, test.wc.isCertTypeOK(test.wh)) + assert.Equal(t, test.want, test.wc.isCertTypeOK(test.wh)) }) } } +// withRequestID is a helper that calls into [requestid.NewContext] and returns +// a new context with the requestID added. +func withRequestID(t *testing.T, ctx context.Context, requestID string) context.Context { + t.Helper() + return requestid.NewContext(ctx, requestID) +} + func TestWebhookController_Enrich(t *testing.T) { cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) type test struct { ctl *WebhookController + ctx context.Context req *webhook.RequestBody responses []*webhook.ResponseBody expectErr bool @@ -129,6 +141,7 @@ func TestWebhookController_Enrich(t *testing.T) { webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}}, TemplateData: x509util.TemplateData{}, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}}, expectErr: false, @@ -143,6 +156,7 @@ func TestWebhookController_Enrich(t *testing.T) { }, TemplateData: x509util.TemplateData{}, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{ {Allow: true, Data: map[string]any{"role": "bar"}}, @@ -166,6 +180,7 @@ func TestWebhookController_Enrich(t *testing.T) { TemplateData: x509util.TemplateData{}, certType: linkedca.Webhook_X509, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{ {Allow: true, Data: map[string]any{"role": "bar"}}, @@ -185,14 +200,15 @@ func TestWebhookController_Enrich(t *testing.T) { TemplateData: x509util.TemplateData{}, options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)}, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{{Allow: true, Data: map[string]any{"role": "bar"}}}, expectErr: false, expectTemplateData: x509util.TemplateData{"Webhooks": map[string]any{"people": map[string]any{"role": "bar"}}}, assertRequest: func(t *testing.T, req *webhook.RequestBody) { key, err := x509.MarshalPKIXPublicKey(cert.PublicKey) - assert.FatalError(t, err) - assert.Equals(t, &webhook.X5CCertificate{ + require.NoError(t, err) + assert.Equal(t, &webhook.X5CCertificate{ Raw: cert.Raw, PublicKey: key, PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(), @@ -207,6 +223,7 @@ func TestWebhookController_Enrich(t *testing.T) { webhooks: []*Webhook{{Name: "people", Kind: "ENRICHING"}}, TemplateData: x509util.TemplateData{}, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{{Allow: false}}, expectErr: true, @@ -221,6 +238,7 @@ func TestWebhookController_Enrich(t *testing.T) { PublicKey: []byte("bad"), })}, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{{Allow: false}}, expectErr: true, @@ -232,19 +250,21 @@ func TestWebhookController_Enrich(t *testing.T) { for i, wh := range test.ctl.webhooks { var j = i ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "reqID", r.Header.Get("X-Request-ID")) + err := json.NewEncoder(w).Encode(test.responses[j]) - assert.FatalError(t, err) + require.NoError(t, err) })) // nolint: gocritic // defer in loop isn't a memory leak defer ts.Close() wh.URL = ts.URL } - err := test.ctl.Enrich(test.req) + err := test.ctl.Enrich(test.ctx, test.req) if (err != nil) != test.expectErr { t.Fatalf("Got err %v, want %v", err, test.expectErr) } - assert.Equals(t, test.expectTemplateData, test.ctl.TemplateData) + assert.Equal(t, test.expectTemplateData, test.ctl.TemplateData) if test.assertRequest != nil { test.assertRequest(t, test.req) } @@ -254,12 +274,11 @@ func TestWebhookController_Enrich(t *testing.T) { func TestWebhookController_Authorize(t *testing.T) { cert, err := pemutil.ReadCertificate("testdata/certs/x5c-leaf.crt", pemutil.WithFirstBlock()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) type test struct { ctl *WebhookController + ctx context.Context req *webhook.RequestBody responses []*webhook.ResponseBody expectErr bool @@ -280,6 +299,7 @@ func TestWebhookController_Authorize(t *testing.T) { client: http.DefaultClient, webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}}, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{{Allow: true}}, expectErr: false, @@ -290,6 +310,7 @@ func TestWebhookController_Authorize(t *testing.T) { webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING", CertType: linkedca.Webhook_X509.String()}}, certType: linkedca.Webhook_SSH, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{{Allow: false}}, expectErr: false, @@ -300,13 +321,14 @@ func TestWebhookController_Authorize(t *testing.T) { webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}}, options: []webhook.RequestBodyOption{webhook.WithX5CCertificate(cert)}, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{{Allow: true}}, expectErr: false, assertRequest: func(t *testing.T, req *webhook.RequestBody) { key, err := x509.MarshalPKIXPublicKey(cert.PublicKey) - assert.FatalError(t, err) - assert.Equals(t, &webhook.X5CCertificate{ + require.NoError(t, err) + assert.Equal(t, &webhook.X5CCertificate{ Raw: cert.Raw, PublicKey: key, PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(), @@ -320,6 +342,7 @@ func TestWebhookController_Authorize(t *testing.T) { client: http.DefaultClient, webhooks: []*Webhook{{Name: "people", Kind: "AUTHORIZING"}}, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{{Allow: false}}, expectErr: true, @@ -332,6 +355,7 @@ func TestWebhookController_Authorize(t *testing.T) { PublicKey: []byte("bad"), })}, }, + ctx: withRequestID(t, context.Background(), "reqID"), req: &webhook.RequestBody{}, responses: []*webhook.ResponseBody{{Allow: false}}, expectErr: true, @@ -342,15 +366,17 @@ func TestWebhookController_Authorize(t *testing.T) { for i, wh := range test.ctl.webhooks { var j = i ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "reqID", r.Header.Get("X-Request-ID")) + err := json.NewEncoder(w).Encode(test.responses[j]) - assert.FatalError(t, err) + require.NoError(t, err) })) // nolint: gocritic // defer in loop isn't a memory leak defer ts.Close() wh.URL = ts.URL } - err := test.ctl.Authorize(test.req) + err := test.ctl.Authorize(test.ctx, test.req) if (err != nil) != test.expectErr { t.Fatalf("Got err %v, want %v", err, test.expectErr) } @@ -366,6 +392,7 @@ func TestWebhook_Do(t *testing.T) { type test struct { webhook Webhook dataArg any + requestID string webhookResponse webhook.ResponseBody expectPath string errStatusCode int @@ -375,6 +402,16 @@ func TestWebhook_Do(t *testing.T) { } tests := map[string]test{ "ok": { + webhook: Webhook{ + ID: "abc123", + Secret: "c2VjcmV0Cg==", + }, + requestID: "reqID", + webhookResponse: webhook.ResponseBody{ + Data: map[string]interface{}{"role": "dba"}, + }, + }, + "ok/no-request-id": { webhook: Webhook{ ID: "abc123", Secret: "c2VjcmV0Cg==", @@ -389,6 +426,7 @@ func TestWebhook_Do(t *testing.T) { Secret: "c2VjcmV0Cg==", BearerToken: "mytoken", }, + requestID: "reqID", webhookResponse: webhook.ResponseBody{ Data: map[string]interface{}{"role": "dba"}, }, @@ -405,6 +443,7 @@ func TestWebhook_Do(t *testing.T) { Password: "mypass", }, }, + requestID: "reqID", webhookResponse: webhook.ResponseBody{ Data: map[string]interface{}{"role": "dba"}, }, @@ -416,7 +455,8 @@ func TestWebhook_Do(t *testing.T) { URL: "/users/{{ .username }}?region={{ .region }}", Secret: "c2VjcmV0Cg==", }, - dataArg: map[string]interface{}{"username": "areed", "region": "central"}, + requestID: "reqID", + dataArg: map[string]interface{}{"username": "areed", "region": "central"}, webhookResponse: webhook.ResponseBody{ Data: map[string]interface{}{"role": "dba"}, }, @@ -451,6 +491,7 @@ func TestWebhook_Do(t *testing.T) { ID: "abc123", Secret: "c2VjcmV0Cg==", }, + requestID: "reqID", webhookResponse: webhook.ResponseBody{ Allow: true, }, @@ -463,6 +504,7 @@ func TestWebhook_Do(t *testing.T) { webhookResponse: webhook.ResponseBody{ Data: map[string]interface{}{"role": "dba"}, }, + requestID: "reqID", errStatusCode: 404, serverErrMsg: "item not found", expectErr: errors.New("Webhook server responded with 404"), @@ -471,17 +513,20 @@ func TestWebhook_Do(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - id := r.Header.Get("X-Smallstep-Webhook-ID") - assert.Equals(t, tc.webhook.ID, id) + if tc.requestID != "" { + assert.Equal(t, tc.requestID, r.Header.Get("X-Request-ID")) + } + + assert.Equal(t, tc.webhook.ID, r.Header.Get("X-Smallstep-Webhook-ID")) sig, err := hex.DecodeString(r.Header.Get("X-Smallstep-Signature")) - assert.FatalError(t, err) + assert.NoError(t, err) body, err := io.ReadAll(r.Body) - assert.FatalError(t, err) + assert.NoError(t, err) secret, err := base64.StdEncoding.DecodeString(tc.webhook.Secret) - assert.FatalError(t, err) + assert.NoError(t, err) h := hmac.New(sha256.New, secret) h.Write(body) mac := h.Sum(nil) @@ -490,19 +535,19 @@ func TestWebhook_Do(t *testing.T) { switch { case tc.webhook.BearerToken != "": ah := fmt.Sprintf("Bearer %s", tc.webhook.BearerToken) - assert.Equals(t, ah, r.Header.Get("Authorization")) + assert.Equal(t, ah, r.Header.Get("Authorization")) case tc.webhook.BasicAuth.Username != "" || tc.webhook.BasicAuth.Password != "": whReq, err := http.NewRequest("", "", http.NoBody) - assert.FatalError(t, err) + require.NoError(t, err) whReq.SetBasicAuth(tc.webhook.BasicAuth.Username, tc.webhook.BasicAuth.Password) ah := whReq.Header.Get("Authorization") - assert.Equals(t, ah, whReq.Header.Get("Authorization")) + assert.Equal(t, ah, whReq.Header.Get("Authorization")) default: - assert.Equals(t, "", r.Header.Get("Authorization")) + assert.Equal(t, "", r.Header.Get("Authorization")) } if tc.expectPath != "" { - assert.Equals(t, tc.expectPath, r.URL.Path+"?"+r.URL.RawQuery) + assert.Equal(t, tc.expectPath, r.URL.Path+"?"+r.URL.RawQuery) } if tc.errStatusCode != 0 { @@ -512,26 +557,33 @@ func TestWebhook_Do(t *testing.T) { reqBody := new(webhook.RequestBody) err = json.Unmarshal(body, reqBody) - assert.FatalError(t, err) - // assert.Equals(t, tc.expectToken, reqBody.Token) + require.NoError(t, err) err = json.NewEncoder(w).Encode(tc.webhookResponse) - assert.FatalError(t, err) + require.NoError(t, err) })) defer ts.Close() tc.webhook.URL = ts.URL + tc.webhook.URL reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr)) - assert.FatalError(t, err) - got, err := tc.webhook.Do(http.DefaultClient, reqBody, tc.dataArg) + require.NoError(t, err) + + ctx := context.Background() + if tc.requestID != "" { + ctx = withRequestID(t, ctx, tc.requestID) + } + ctx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + got, err := tc.webhook.DoWithContext(ctx, http.DefaultClient, reqBody, tc.dataArg) if tc.expectErr != nil { - assert.Equals(t, tc.expectErr.Error(), err.Error()) + assert.Equal(t, tc.expectErr.Error(), err.Error()) return } - assert.FatalError(t, err) + assert.NoError(t, err) - assert.Equals(t, got, &tc.webhookResponse) + assert.Equal(t, &tc.webhookResponse, got) }) } @@ -544,7 +596,7 @@ func TestWebhook_Do(t *testing.T) { URL: ts.URL, } cert, err := tls.LoadX509KeyPair("testdata/certs/foo.crt", "testdata/secrets/foo.key") - assert.FatalError(t, err) + require.NoError(t, err) transport := http.DefaultTransport.(*http.Transport).Clone() transport.TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, @@ -554,12 +606,19 @@ func TestWebhook_Do(t *testing.T) { Transport: transport, } reqBody, err := webhook.NewRequestBody(webhook.WithX509CertificateRequest(csr)) - assert.FatalError(t, err) - _, err = wh.Do(client, reqBody, nil) - assert.FatalError(t, err) + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + _, err = wh.DoWithContext(ctx, client, reqBody, nil) + require.NoError(t, err) + + ctx, cancel = context.WithTimeout(context.Background(), time.Second*10) + defer cancel() wh.DisableTLSClientAuth = true - _, err = wh.Do(client, reqBody, nil) - assert.Error(t, err) + _, err = wh.DoWithContext(ctx, client, reqBody, nil) + require.Error(t, err) }) } diff --git a/authority/provisioners.go b/authority/provisioners.go index 551411de..34cc75ed 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -608,19 +608,19 @@ func provisionerWebhookToLinkedca(pwh *provisioner.Webhook) *linkedca.Webhook { } func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.Duration, err error) { - if len(d.Min) > 0 { + if d.Min != "" { min, err = provisioner.NewDuration(d.Min) if err != nil { return nil, nil, nil, admin.WrapErrorISE(err, "error parsing minimum duration '%s'", d.Min) } } - if len(d.Max) > 0 { + if d.Max != "" { max, err = provisioner.NewDuration(d.Max) if err != nil { return nil, nil, nil, admin.WrapErrorISE(err, "error parsing maximum duration '%s'", d.Max) } } - if len(d.Default) > 0 { + if d.Default != "" { def, err = provisioner.NewDuration(d.Default) if err != nil { return nil, nil, nil, admin.WrapErrorISE(err, "error parsing default duration '%s'", d.Default) diff --git a/authority/provisioners_test.go b/authority/provisioners_test.go index f6af6f54..f62f8127 100644 --- a/authority/provisioners_test.go +++ b/authority/provisioners_test.go @@ -149,7 +149,7 @@ func TestAuthority_LoadProvisionerByCertificate(t *testing.T) { opts, err := a.Authorize(ctx, token) require.NoError(t, err) opts = append(opts, extraOpts...) - certs, err := a.Sign(csr, provisioner.SignOptions{}, opts...) + certs, err := a.SignWithContext(ctx, csr, provisioner.SignOptions{}, opts...) require.NoError(t, err) return certs[0] } diff --git a/authority/root.go b/authority/root.go index f391997f..37038cfa 100644 --- a/authority/root.go +++ b/authority/root.go @@ -45,7 +45,7 @@ func (a *Authority) GetRoots() ([]*x509.Certificate, error) { // GetFederation returns all the root certificates in the federation. // This method implements the Authority interface. func (a *Authority) GetFederation() (federation []*x509.Certificate, err error) { - a.certificates.Range(func(k, v interface{}) bool { + a.certificates.Range(func(_, v interface{}) bool { crt, ok := v.(*x509.Certificate) if !ok { federation = nil diff --git a/authority/ssh.go b/authority/ssh.go index 868dd013..30e4bfc7 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -146,7 +146,13 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user, hostname string) (* } // SignSSH creates a signed SSH certificate with the given public key and options. -func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { +func (a *Authority) SignSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { + cert, prov, err := a.signSSH(ctx, key, opts, signOpts...) + a.meter.SSHSigned(prov, err) + return cert, err +} + +func (a *Authority) signSSH(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) { var ( certOptions []sshutil.Option mods []provisioner.SSHCertModifier @@ -156,10 +162,10 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision // Validate given key and options if key == nil { - return nil, errs.BadRequest("ssh public key cannot be nil") + return nil, nil, errs.BadRequest("ssh public key cannot be nil") } if err := opts.Validate(); err != nil { - return nil, err + return nil, nil, err } // Set backdate with the configured value @@ -192,7 +198,7 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision // validate the given SSHOptions case provisioner.SSHCertOptionsValidator: if err := o.Valid(opts); err != nil { - return nil, errs.BadRequestErr(err, "error validating ssh certificate options") + return nil, prov, errs.BadRequestErr(err, "error validating ssh certificate options") } // call webhooks @@ -200,14 +206,14 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision webhookCtl = o default: - return nil, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o) + return nil, prov, errs.InternalServer("authority.SignSSH: invalid extra option type %T", o) } } // Validate public key for _, v := range keyValidators { if err := v.Valid(key); err != nil { - return nil, errs.ApplyOptions( + return nil, nil, errs.ApplyOptions( errs.ForbiddenErr(err, err.Error()), errs.WithKeyVal("signOptions", signOpts), ) @@ -223,8 +229,8 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision } // Call enriching webhooks - if err := callEnrichingWebhooksSSH(webhookCtl, cr); err != nil { - return nil, errs.ApplyOptions( + if err := a.callEnrichingWebhooksSSH(ctx, prov, webhookCtl, cr); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, err.Error()), errs.WithKeyVal("signOptions", signOpts), ) @@ -234,20 +240,21 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision certificate, err := sshutil.NewCertificate(cr, certOptions...) if err != nil { var te *sshutil.TemplateError - if errors.As(err, &te) { - return nil, errs.ApplyOptions( + switch { + case errors.As(err, &te): + return nil, prov, errs.ApplyOptions( errs.BadRequestErr(err, err.Error()), errs.WithKeyVal("signOptions", signOpts), ) - } - // explicitly check for unmarshaling errors, which are most probably caused by JSON template syntax errors - if strings.HasPrefix(err.Error(), "error unmarshaling certificate") { - return nil, errs.InternalServerErr(templatingError(err), + case strings.HasPrefix(err.Error(), "error unmarshaling certificate"): + // explicitly check for unmarshaling errors, which are most probably caused by JSON template syntax errors + return nil, prov, errs.InternalServerErr(templatingError(err), errs.WithKeyVal("signOptions", signOpts), errs.WithMessage("error applying certificate template"), ) + default: + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH") } - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH") } // Get actual *ssh.Certificate and continue with provisioner modifiers. @@ -256,13 +263,13 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision // Use SignSSHOptions to modify the certificate validity. It will be later // checked or set if not defined. if err := opts.ModifyValidity(certTpl); err != nil { - return nil, errs.BadRequestErr(err, err.Error()) + return nil, prov, errs.BadRequestErr(err, err.Error()) } // Use provisioner modifiers. for _, m := range mods { if err := m.Modify(certTpl, opts); err != nil { - return nil, errs.ForbiddenErr(err, "error creating ssh certificate") + return nil, prov, errs.ForbiddenErr(err, "error creating ssh certificate") } } @@ -271,32 +278,32 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision switch certTpl.CertType { case ssh.UserCert: if a.sshCAUserCertSignKey == nil { - return nil, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled") + return nil, prov, errs.NotImplemented("authority.SignSSH: user certificate signing is not enabled") } signer = a.sshCAUserCertSignKey case ssh.HostCert: if a.sshCAHostCertSignKey == nil { - return nil, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled") + return nil, prov, errs.NotImplemented("authority.SignSSH: host certificate signing is not enabled") } signer = a.sshCAHostCertSignKey default: - return nil, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType) + return nil, prov, errs.InternalServer("authority.SignSSH: unexpected ssh certificate type: %d", certTpl.CertType) } // Check if authority is allowed to sign the certificate if err := a.isAllowedToSignSSHCertificate(certTpl); err != nil { var ee *errs.Error if errors.As(err, &ee) { - return nil, ee + return nil, prov, ee } - return nil, errs.InternalServerErr(err, + return nil, prov, errs.InternalServerErr(err, errs.WithMessage("authority.SignSSH: error creating ssh certificate"), ) } // Send certificate to webhooks for authorization - if err := callAuthorizingWebhooksSSH(webhookCtl, certificate, certTpl); err != nil { - return nil, errs.ApplyOptions( + if err := a.callAuthorizingWebhooksSSH(ctx, prov, webhookCtl, certificate, certTpl); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "authority.SignSSH: error signing certificate"), ) } @@ -304,21 +311,21 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision // Sign certificate. cert, err := sshutil.CreateCertificate(certTpl, signer) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate") + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error signing certificate") } // User provisioners validators. for _, v := range validators { if err := v.Valid(cert, opts); err != nil { - return nil, errs.ForbiddenErr(err, "error validating ssh certificate") + return nil, prov, errs.ForbiddenErr(err, "error validating ssh certificate") } } - if err = a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db") + if err := a.storeSSHCertificate(prov, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.SignSSH: error storing certificate in db") } - return cert, nil + return cert, prov, nil } // isAllowedToSignSSHCertificate checks if the Authority is allowed to sign the SSH certificate. @@ -328,12 +335,18 @@ func (a *Authority) isAllowedToSignSSHCertificate(cert *ssh.Certificate) error { // RenewSSH creates a signed SSH certificate using the old SSH certificate as a template. func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, error) { + cert, prov, err := a.renewSSH(ctx, oldCert) + a.meter.SSHRenewed(prov, err) + return cert, err +} + +func (a *Authority) renewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ssh.Certificate, provisioner.Interface, error) { if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 { - return nil, errs.BadRequest("cannot renew a certificate without validity period") + return nil, nil, errs.BadRequest("cannot renew a certificate without validity period") } if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil { - return nil, err + return nil, nil, err } // Attempt to extract the provisioner from the token. @@ -366,36 +379,41 @@ func (a *Authority) RenewSSH(ctx context.Context, oldCert *ssh.Certificate) (*ss switch certTpl.CertType { case ssh.UserCert: if a.sshCAUserCertSignKey == nil { - return nil, errs.NotImplemented("renewSSH: user certificate signing is not enabled") + return nil, prov, errs.NotImplemented("renewSSH: user certificate signing is not enabled") } signer = a.sshCAUserCertSignKey case ssh.HostCert: if a.sshCAHostCertSignKey == nil { - return nil, errs.NotImplemented("renewSSH: host certificate signing is not enabled") + return nil, prov, errs.NotImplemented("renewSSH: host certificate signing is not enabled") } signer = a.sshCAHostCertSignKey default: - return nil, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType) + return nil, prov, errs.InternalServer("renewSSH: unexpected ssh certificate type: %d", certTpl.CertType) } // Sign certificate. cert, err := sshutil.CreateCertificate(certTpl, signer) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") } - if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db") + if err := a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "renewSSH: error storing certificate in db") } - return cert, nil + return cert, prov, nil } // RekeySSH creates a signed SSH certificate using the old SSH certificate as a template. func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { - var validators []provisioner.SSHCertValidator + cert, prov, err := a.rekeySSH(ctx, oldCert, pub, signOpts...) + a.meter.SSHRekeyed(prov, err) + return cert, err +} +func (a *Authority) rekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, provisioner.Interface, error) { var prov provisioner.Interface + var validators []provisioner.SSHCertValidator for _, op := range signOpts { switch o := op.(type) { // Capture current provisioner @@ -405,16 +423,16 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub case provisioner.SSHCertValidator: validators = append(validators, o) default: - return nil, errs.InternalServer("rekeySSH; invalid extra option type %T", o) + return nil, prov, errs.InternalServer("rekeySSH; invalid extra option type %T", o) } } if oldCert.ValidAfter == 0 || oldCert.ValidBefore == 0 { - return nil, errs.BadRequest("cannot rekey a certificate without validity period") + return nil, prov, errs.BadRequest("cannot rekey a certificate without validity period") } if err := a.authorizeSSHCertificate(ctx, oldCert); err != nil { - return nil, err + return nil, prov, err } backdate := a.config.AuthorityConfig.Backdate.Duration @@ -441,37 +459,37 @@ func (a *Authority) RekeySSH(ctx context.Context, oldCert *ssh.Certificate, pub switch cert.CertType { case ssh.UserCert: if a.sshCAUserCertSignKey == nil { - return nil, errs.NotImplemented("rekeySSH; user certificate signing is not enabled") + return nil, prov, errs.NotImplemented("rekeySSH; user certificate signing is not enabled") } signer = a.sshCAUserCertSignKey case ssh.HostCert: if a.sshCAHostCertSignKey == nil { - return nil, errs.NotImplemented("rekeySSH; host certificate signing is not enabled") + return nil, prov, errs.NotImplemented("rekeySSH; host certificate signing is not enabled") } signer = a.sshCAHostCertSignKey default: - return nil, errs.BadRequest("unexpected certificate type '%d'", cert.CertType) + return nil, prov, errs.BadRequest("unexpected certificate type '%d'", cert.CertType) } var err error // Sign certificate. cert, err = sshutil.CreateCertificate(cert, signer) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "signSSH: error signing certificate") } // Apply validators from provisioner. for _, v := range validators { if err := v.Valid(cert, provisioner.SignSSHOptions{Backdate: backdate}); err != nil { - return nil, errs.ForbiddenErr(err, "error validating ssh certificate") + return nil, prov, errs.ForbiddenErr(err, "error validating ssh certificate") } } - if err = a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db") + if err := a.storeRenewedSSHCertificate(prov, oldCert, cert); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "rekeySSH; error storing certificate in db") } - return cert, nil + return cert, prov, nil } func (a *Authority) storeSSHCertificate(prov provisioner.Interface, cert *ssh.Certificate) error { @@ -671,28 +689,34 @@ func (a *Authority) getAddUserCommand(principal string) string { return strings.ReplaceAll(cmd, "", principal) } -func callEnrichingWebhooksSSH(webhookCtl webhookController, cr sshutil.CertificateRequest) error { +func (a *Authority) callEnrichingWebhooksSSH(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, cr sshutil.CertificateRequest) (err error) { if webhookCtl == nil { - return nil + return } - whEnrichReq, err := webhook.NewRequestBody( + defer func() { a.meter.SSHWebhookEnriched(prov, err) }() + + var whEnrichReq *webhook.RequestBody + if whEnrichReq, err = webhook.NewRequestBody( webhook.WithSSHCertificateRequest(cr), - ) - if err != nil { - return err + ); err == nil { + err = webhookCtl.Enrich(ctx, whEnrichReq) } - return webhookCtl.Enrich(whEnrichReq) + + return } -func callAuthorizingWebhooksSSH(webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) error { +func (a *Authority) callAuthorizingWebhooksSSH(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, cert *sshutil.Certificate, certTpl *ssh.Certificate) (err error) { if webhookCtl == nil { - return nil + return } - whAuthBody, err := webhook.NewRequestBody( + defer func() { a.meter.SSHWebhookAuthorized(prov, err) }() + + var whAuthBody *webhook.RequestBody + if whAuthBody, err = webhook.NewRequestBody( webhook.WithSSHCertificate(cert, certTpl), - ) - if err != nil { - return err + ); err == nil { + err = webhookCtl.Authorize(ctx, whAuthBody) } - return webhookCtl.Authorize(whAuthBody) + + return } diff --git a/authority/tls.go b/authority/tls.go index 6e967920..ebc9d0d8 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -59,7 +59,7 @@ var ( ) func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { - return func(crt *x509.Certificate, opts provisioner.SignOptions) error { + return func(crt *x509.Certificate, _ provisioner.SignOptions) error { if def == nil { return errors.New("default ASN1DN template cannot be nil") } @@ -91,8 +91,23 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { } } -// Sign creates a signed certificate from a certificate signing request. +// Sign creates a signed certificate from a certificate signing request. It +// creates a new context.Context, and calls into SignWithContext. +// +// Deprecated: Use authority.SignWithContext with an actual context.Context. func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + return a.SignWithContext(context.Background(), csr, signOpts, extraOpts...) +} + +// SignWithContext creates a signed certificate from a certificate signing +// request, taking the provided context.Context. +func (a *Authority) SignWithContext(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { + chain, prov, err := a.signX509(ctx, csr, signOpts, extraOpts...) + a.meter.X509Signed(prov, err) + return chain, err +} + +func (a *Authority) signX509(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, provisioner.Interface, error) { var ( certOptions []x509util.Option certValidators []provisioner.CertificateValidator @@ -100,9 +115,9 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign certEnforcers []provisioner.CertificateEnforcer ) - opts := []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} + opts := []any{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} if err := csr.CheckSignature(); err != nil { - return nil, errs.ApplyOptions( + return nil, nil, errs.ApplyOptions( errs.BadRequestErr(err, "invalid certificate request"), opts..., ) @@ -111,10 +126,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Set backdate with the configured value signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration - var prov provisioner.Interface - var pInfo *casapi.ProvisionerInfo - var attData *provisioner.AttestationData - var webhookCtl webhookController + var ( + prov provisioner.Interface + pInfo *casapi.ProvisionerInfo + attData *provisioner.AttestationData + webhookCtl webhookController + ) for _, op := range extraOpts { switch k := op.(type) { // Capture current provisioner @@ -132,8 +149,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Validate the given certificate request. case provisioner.CertificateRequestValidator: if err := k.Valid(csr); err != nil { - return nil, errs.ApplyOptions( - errs.ForbiddenErr(err, "error validating certificate"), + return nil, prov, errs.ApplyOptions( + errs.ForbiddenErr(err, "error validating certificate request"), opts..., ) } @@ -159,45 +176,46 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign webhookCtl = k default: - return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...) + return nil, prov, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]any{k}, opts...)...) } } - if err := callEnrichingWebhooksX509(webhookCtl, attData, csr); err != nil { - return nil, errs.ApplyOptions( + if err := a.callEnrichingWebhooksX509(ctx, prov, webhookCtl, attData, csr); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, err.Error()), errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts), ) } - cert, err := x509util.NewCertificate(csr, certOptions...) + crt, err := x509util.NewCertificate(csr, certOptions...) if err != nil { var te *x509util.TemplateError - if errors.As(err, &te) { - return nil, errs.ApplyOptions( + switch { + case errors.As(err, &te): + return nil, prov, errs.ApplyOptions( errs.BadRequestErr(err, err.Error()), errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts), ) - } - // explicitly check for unmarshaling errors, which are most probably caused by JSON template (syntax) errors - if strings.HasPrefix(err.Error(), "error unmarshaling certificate") { - return nil, errs.InternalServerErr(templatingError(err), + case strings.HasPrefix(err.Error(), "error unmarshaling certificate"): + // explicitly check for unmarshaling errors, which are most probably caused by JSON template (syntax) errors + return nil, prov, errs.InternalServerErr(templatingError(err), errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts), errs.WithMessage("error applying certificate template"), ) + default: + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...) } - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...) } // Certificate modifiers before validation - leaf := cert.GetCertificate() + leaf := crt.GetCertificate() // Set default subject if err := withDefaultASN1DN(a.config.AuthorityConfig.Template).Modify(leaf, signOpts); err != nil { - return nil, errs.ApplyOptions( + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -205,7 +223,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign for _, m := range certModifiers { if err := m.Modify(leaf, signOpts); err != nil { - return nil, errs.ApplyOptions( + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -215,7 +233,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Certificate validation. for _, v := range certValidators { if err := v.Valid(leaf, signOpts); err != nil { - return nil, errs.ApplyOptions( + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error validating certificate"), opts..., ) @@ -224,8 +242,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Certificate modifiers after validation for _, m := range certEnforcers { - if err := m.Enforce(leaf); err != nil { - return nil, errs.ApplyOptions( + if err = m.Enforce(leaf); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -234,8 +252,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Process injected modifiers after validation for _, m := range a.x509Enforcers { - if err := m.Enforce(leaf); err != nil { - return nil, errs.ApplyOptions( + if err = m.Enforce(leaf); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -243,12 +261,12 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign } // Check if authority is allowed to sign the certificate - if err := a.isAllowedToSignX509Certificate(leaf); err != nil { + if err = a.isAllowedToSignX509Certificate(leaf); err != nil { var ee *errs.Error if errors.As(err, &ee) { - return nil, errs.ApplyOptions(ee, opts...) + return nil, prov, errs.ApplyOptions(ee, opts...) } - return nil, errs.InternalServerErr(err, + return nil, prov, errs.InternalServerErr(err, errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts), errs.WithMessage("error creating certificate"), @@ -256,8 +274,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign } // Send certificate to webhooks for authorization - if err := callAuthorizingWebhooksX509(webhookCtl, cert, leaf, attData); err != nil { - return nil, errs.ApplyOptions( + if err := a.callAuthorizingWebhooksX509(ctx, prov, webhookCtl, crt, leaf, attData); err != nil { + return nil, prov, errs.ApplyOptions( errs.ForbiddenErr(err, "error creating certificate"), opts..., ) @@ -265,6 +283,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign // Sign certificate lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate)) + resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ Template: leaf, CSR: csr, @@ -273,23 +292,22 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign Provisioner: pInfo, }) if err != nil { - return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...) + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...) } - fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) + chain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) - // Wrap provisioner with extra information. - prov = wrapProvisioner(prov, attData) + // Wrap provisioner with extra information, if not nil + if prov != nil { + prov = wrapProvisioner(prov, attData) + } // Store certificate in the db. - if err = a.storeCertificate(prov, fullchain); err != nil { - if !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.Wrap(http.StatusInternalServerError, err, - "authority.Sign; error storing certificate in db", opts...) - } + if err := a.storeCertificate(prov, chain); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error storing certificate in db", opts...) } - return fullchain, nil + return chain, prov, nil } // isAllowedToSignX509Certificate checks if the Authority is allowed @@ -337,14 +355,25 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5 // of rekey), and 'NotBefore/NotAfter' (the validity duration of the new // certificate should be equal to the old one, but starting 'now'). func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { + chain, prov, err := a.renewContext(ctx, oldCert, pk) + if pk == nil { + a.meter.X509Renewed(prov, err) + } else { + a.meter.X509Rekeyed(prov, err) + } + return chain, err +} + +func (a *Authority) renewContext(ctx context.Context, oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, provisioner.Interface, error) { isRekey := (pk != nil) opts := []errs.Option{ errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), } // Check step provisioner extensions - if err := a.authorizeRenew(ctx, oldCert); err != nil { - return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) + prov, err := a.authorizeRenew(ctx, oldCert) + if err != nil { + return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) } // Durations @@ -414,15 +443,17 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, // // TODO(hslatman,maraino): consider adding policies too and consider if // RenewSSH should check policies. - if err := a.constraintsEngine.ValidateCertificate(newCert); err != nil { + if err = a.constraintsEngine.ValidateCertificate(newCert); err != nil { var ee *errs.Error - if errors.As(err, &ee) { - return nil, errs.StatusCodeError(ee.StatusCode(), err, opts...) + switch { + case errors.As(err, &ee): + return nil, prov, errs.StatusCodeError(ee.StatusCode(), err, opts...) + default: + return nil, prov, errs.InternalServerErr(err, + errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), + errs.WithMessage("error renewing certificate"), + ) } - return nil, errs.InternalServerErr(err, - errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String()), - errs.WithMessage("error renewing certificate"), - ) } // The token can optionally be in the context. If the CA is running in RA @@ -436,17 +467,16 @@ func (a *Authority) RenewContext(ctx context.Context, oldCert *x509.Certificate, Token: token, }) if err != nil { - return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) + return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) } - fullchain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) - if err = a.storeRenewedCertificate(oldCert, fullchain); err != nil { - if !errors.Is(err, db.ErrNotImplemented) { - return nil, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) - } + chain := append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...) + + if err = a.storeRenewedCertificate(oldCert, chain); err != nil && !errors.Is(err, db.ErrNotImplemented) { + return nil, prov, errs.StatusCodeError(http.StatusInternalServerError, err, opts...) } - return fullchain, nil + return chain, prov, nil } // storeCertificate allows to use an extension of the db.AuthDB interface that @@ -675,9 +705,17 @@ func (a *Authority) revokeSSH(crt *ssh.Certificate, rci *db.RevokedCertificateIn return a.db.RevokeSSH(rci) } +// CertificateRevocationListInfo contains a CRL in DER format and associated metadata. +type CertificateRevocationListInfo struct { + Number int64 + ExpiresAt time.Time + Duration time.Duration + Data []byte +} + // GetCertificateRevocationList will return the currently generated CRL from the DB, or a not implemented // error if the underlying AuthDB does not support CRLs -func (a *Authority) GetCertificateRevocationList() ([]byte, error) { +func (a *Authority) GetCertificateRevocationList() (*CertificateRevocationListInfo, error) { if !a.config.CRL.IsEnabled() { return nil, errs.Wrap(http.StatusNotFound, errors.Errorf("Certificate Revocation Lists are not enabled"), "authority.GetCertificateRevocationList") } @@ -692,7 +730,12 @@ func (a *Authority) GetCertificateRevocationList() ([]byte, error) { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetCertificateRevocationList") } - return crlInfo.DER, nil + return &CertificateRevocationListInfo{ + Number: crlInfo.Number, + ExpiresAt: crlInfo.ExpiresAt, + Duration: crlInfo.Duration, + Data: crlInfo.DER, + }, nil } // GenerateCertificateRevocationList generates a DER representation of a signed CRL and stores it in the @@ -870,10 +913,19 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { return fatal(err) } + // Set the cert lifetime as follows: + // i) If the CA is not a StepCAS RA use 24h, else + // ii) if the CA is a StepCAS RA, leave the lifetime empty and + // let the provisioner of the CA decide the lifetime of the RA cert. + var lifetime time.Duration + if casapi.TypeOf(a.x509CAService) != casapi.StepCAS { + lifetime = 24 * time.Hour + } + resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{ Template: certTpl, CSR: cr, - Lifetime: 24 * time.Hour, + Lifetime: lifetime, Backdate: 1 * time.Minute, IsCAServerCert: true, }) @@ -952,42 +1004,50 @@ func templatingError(err error) error { return errors.Wrap(cause, "error applying certificate template") } -func callEnrichingWebhooksX509(webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) error { +func (a *Authority) callEnrichingWebhooksX509(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, attData *provisioner.AttestationData, csr *x509.CertificateRequest) (err error) { if webhookCtl == nil { - return nil + return } + defer func() { a.meter.X509WebhookEnriched(prov, err) }() + var attested *webhook.AttestationData if attData != nil { attested = &webhook.AttestationData{ PermanentIdentifier: attData.PermanentIdentifier, } } - whEnrichReq, err := webhook.NewRequestBody( + + var whEnrichReq *webhook.RequestBody + if whEnrichReq, err = webhook.NewRequestBody( webhook.WithX509CertificateRequest(csr), webhook.WithAttestationData(attested), - ) - if err != nil { - return err + ); err == nil { + err = webhookCtl.Enrich(ctx, whEnrichReq) } - return webhookCtl.Enrich(whEnrichReq) + + return } -func callAuthorizingWebhooksX509(webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) error { +func (a *Authority) callAuthorizingWebhooksX509(ctx context.Context, prov provisioner.Interface, webhookCtl webhookController, cert *x509util.Certificate, leaf *x509.Certificate, attData *provisioner.AttestationData) (err error) { if webhookCtl == nil { - return nil + return } + defer func() { a.meter.X509WebhookAuthorized(prov, err) }() + var attested *webhook.AttestationData if attData != nil { attested = &webhook.AttestationData{ PermanentIdentifier: attData.PermanentIdentifier, } } - whAuthBody, err := webhook.NewRequestBody( + + var whAuthBody *webhook.RequestBody + if whAuthBody, err = webhook.NewRequestBody( webhook.WithX509Certificate(cert, leaf), webhook.WithAttestationData(attested), - ) - if err != nil { - return err + ); err == nil { + err = webhookCtl.Authorize(ctx, whAuthBody) } - return webhookCtl.Authorize(whAuthBody) + + return } diff --git a/authority/tls_test.go b/authority/tls_test.go index f0192fea..70bcd45f 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -15,6 +15,7 @@ import ( "fmt" "net/http" "reflect" + "strings" "testing" "time" @@ -25,7 +26,6 @@ import ( "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" - "github.com/smallstep/assert" "github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/policy" @@ -34,6 +34,8 @@ import ( "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" "github.com/smallstep/nosql/database" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( @@ -81,25 +83,25 @@ func generateCertificate(t *testing.T, commonName string, sans []string, opts .. t.Helper() priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - assert.FatalError(t, err) + require.NoError(t, err) cr, err := x509util.CreateCertificateRequest(commonName, sans, priv) - assert.FatalError(t, err) + require.NoError(t, err) template, err := x509util.NewCertificate(cr) - assert.FatalError(t, err) + require.NoError(t, err) cert := template.GetCertificate() for _, m := range opts { switch m := m.(type) { case provisioner.CertificateModifierFunc: err = m.Modify(cert, provisioner.SignOptions{}) - assert.FatalError(t, err) + require.NoError(t, err) case signerFunc: cert, err = m(cert, priv.Public()) - assert.FatalError(t, err) + require.NoError(t, err) default: - t.Fatalf("unknown type %T", m) + require.Fail(t, "", "unknown type %T", m) } } @@ -109,36 +111,36 @@ func generateCertificate(t *testing.T, commonName string, sans []string, opts .. func generateRootCertificate(t *testing.T) (*x509.Certificate, crypto.Signer) { t.Helper() priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - assert.FatalError(t, err) + require.NoError(t, err) cr, err := x509util.CreateCertificateRequest("TestRootCA", nil, priv) - assert.FatalError(t, err) + require.NoError(t, err) data := x509util.CreateTemplateData("TestRootCA", nil) template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data)) - assert.FatalError(t, err) + require.NoError(t, err) cert := template.GetCertificate() cert, err = x509util.CreateCertificate(cert, cert, priv.Public(), priv) - assert.FatalError(t, err) + require.NoError(t, err) return cert, priv } func generateIntermidiateCertificate(t *testing.T, issuer *x509.Certificate, signer crypto.Signer) (*x509.Certificate, crypto.Signer) { t.Helper() priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - assert.FatalError(t, err) + require.NoError(t, err) cr, err := x509util.CreateCertificateRequest("TestIntermediateCA", nil, priv) - assert.FatalError(t, err) + require.NoError(t, err) data := x509util.CreateTemplateData("TestIntermediateCA", nil) template, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data)) - assert.FatalError(t, err) + require.NoError(t, err) cert := template.GetCertificate() cert, err = x509util.CreateCertificate(cert, issuer, priv.Public(), signer) - assert.FatalError(t, err) + require.NoError(t, err) return cert, priv } @@ -193,9 +195,9 @@ func getCSR(t *testing.T, priv interface{}, opts ...func(*x509.CertificateReques opt(_csr) } csrBytes, err := x509.CreateCertificateRequest(rand.Reader, _csr, priv) - assert.FatalError(t, err) + require.NoError(t, err) csr, err := x509.ParseCertificateRequest(csrBytes) - assert.FatalError(t, err) + require.NoError(t, err) return csr } @@ -238,12 +240,17 @@ func (e *testEnforcer) Enforce(cert *x509.Certificate) error { return nil } -func TestAuthority_Sign(t *testing.T) { +func assertHasPrefix(t *testing.T, s, p string, msg ...interface{}) bool { + t.Helper() + return assert.True(t, strings.HasPrefix(s, p), "%q is not a prefix of %q", p, s) +} + +func TestAuthority_SignWithContext(t *testing.T) { pub, priv, err := keyutil.GenerateDefaultKeyPair() - assert.FatalError(t, err) + require.NoError(t, err) a := testAuthority(t) - assert.FatalError(t, err) + require.NoError(t, err) a.config.AuthorityConfig.Template = &ASN1DN{ Country: "Tazmania", Organization: "Acme Co", @@ -263,12 +270,12 @@ func TestAuthority_Sign(t *testing.T) { // Create a token to get test extra opts. p := a.config.AuthorityConfig.Provisioners[1].(*provisioner.JWK) key, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass"))) - assert.FatalError(t, err) + require.NoError(t, err) token, err := generateToken("smallstep test", "step-cli", testAudiences.Sign[0], []string{"test.smallstep.com"}, time.Now(), key) - assert.FatalError(t, err) + require.NoError(t, err) ctx := provisioner.NewContextWithMethod(context.Background(), provisioner.SignMethod) extraOpts, err := a.Authorize(ctx, token) - assert.FatalError(t, err) + require.NoError(t, err) type signTest struct { auth *Authority @@ -373,9 +380,9 @@ W5kR63lNVHBHgQmv5mA8YFsfrJHstaz5k727v2LMHEYIf5/3i16d5zhuxUoaPTYr ZYtQ9Ot36qc= -----END CERTIFICATE REQUEST-----` block, _ := pem.Decode([]byte(shortRSAKeyPEM)) - assert.FatalError(t, err) + require.NoError(t, err) csr, err := x509.ParseCertificateRequest(block.Bytes) - assert.FatalError(t, err) + require.NoError(t, err) return &signTest{ auth: a, @@ -414,10 +421,10 @@ ZYtQ9Ot36qc= X509: &provisioner.X509Options{Template: `{{ fail "fail message" }}`}, } testExtraOpts, err := testAuthority.Authorize(ctx, token) - assert.FatalError(t, err) + require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -443,10 +450,10 @@ ZYtQ9Ot36qc= }, } testExtraOpts, err := testAuthority.Authorize(ctx, token) - assert.FatalError(t, err) + require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -472,10 +479,10 @@ ZYtQ9Ot36qc= }, } testExtraOpts, err := testAuthority.Authorize(ctx, token) - assert.FatalError(t, err) + require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -493,7 +500,7 @@ ZYtQ9Ot36qc= aa := testAuthority(t) aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -518,7 +525,7 @@ ZYtQ9Ot36qc= })) aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -538,7 +545,7 @@ ZYtQ9Ot36qc= aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { fmt.Println(crt.Subject) - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -550,7 +557,7 @@ ZYtQ9Ot36qc= }, } engine, err := policy.New(options) - assert.FatalError(t, err) + require.NoError(t, err) aa.policyEngine = engine return &signTest{ auth: aa, @@ -604,8 +611,8 @@ ZYtQ9Ot36qc= return true, nil }, MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") - assert.Equals(t, crt.DNSNames, []string{"test.smallstep.com"}) + assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.DNSNames, []string{"test.smallstep.com"}) return nil }, } @@ -615,10 +622,10 @@ ZYtQ9Ot36qc= "sans": []string{"test.smallstep.com"}, "cnf": map[string]any{"kid": "bad-fingerprint"}, }) - assert.FatalError(t, err) + require.NoError(t, err) opts, err := auth.Authorize(ctx, tok) - assert.FatalError(t, err) + require.NoError(t, err) return &signTest{ auth: auth, @@ -636,7 +643,7 @@ ZYtQ9Ot36qc= _a := testAuthority(t) _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -655,7 +662,7 @@ ZYtQ9Ot36qc= bcExt.Id = asn1.ObjectIdentifier{2, 5, 29, 19} bcExt.Critical = false bcExt.Value, err = asn1.Marshal(basicConstraints{IsCA: true, MaxPathLen: 4}) - assert.FatalError(t, err) + require.NoError(t, err) csr := getCSR(t, priv, setExtraExtsCSR([]pkix.Extension{ bcExt, @@ -670,7 +677,7 @@ ZYtQ9Ot36qc= _a := testAuthority(t) _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -701,10 +708,10 @@ ZYtQ9Ot36qc= }`}, } testExtraOpts, err := testAuthority.Authorize(ctx, token) - assert.FatalError(t, err) + require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -735,10 +742,10 @@ ZYtQ9Ot36qc= }`}, } testExtraOpts, err := testAuthority.Authorize(ctx, token) - assert.FatalError(t, err) + require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -775,7 +782,7 @@ ZYtQ9Ot36qc= _a.config.AuthorityConfig.Template = &ASN1DN{} _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject, pkix.Name{}) + assert.Equal(t, crt.Subject, pkix.Name{}) return nil }, } @@ -800,8 +807,8 @@ ZYtQ9Ot36qc= aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") - assert.Equals(t, crt.CRLDistributionPoints, []string{"http://ca.example.org/leaf.crl"}) + assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.CRLDistributionPoints, []string{"http://ca.example.org/leaf.crl"}) return nil }, } @@ -821,7 +828,7 @@ ZYtQ9Ot36qc= aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -834,7 +841,7 @@ ZYtQ9Ot36qc= }, } engine, err := policy.New(options) - assert.FatalError(t, err) + require.NoError(t, err) aa.policyEngine = engine return &signTest{ auth: aa, @@ -854,13 +861,13 @@ ZYtQ9Ot36qc= MStoreCertificateChain: func(prov provisioner.Interface, certs ...*x509.Certificate) error { p, ok := prov.(attProvisioner) if assert.True(t, ok) { - assert.Equals(t, &provisioner.AttestationData{ + assert.Equal(t, &provisioner.AttestationData{ PermanentIdentifier: "1234567890", }, p.AttestationData()) } - if assert.Len(t, 2, certs) { - assert.Equals(t, certs[0].Subject.CommonName, "smallstep test") - assert.Equals(t, certs[1].Subject.CommonName, "smallstep Intermediate CA") + 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") } return nil }, @@ -881,7 +888,7 @@ ZYtQ9Ot36qc= "ok with cnf": func(t *testing.T) *signTest { csr := getCSR(t, priv) fingerprint, err := fingerprint.New(csr.Raw, crypto.SHA256, fingerprint.Base64RawURLFingerprint) - assert.FatalError(t, err) + require.NoError(t, err) auth := testAuthority(t) auth.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template @@ -890,8 +897,8 @@ ZYtQ9Ot36qc= return true, nil }, MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equals(t, crt.Subject.CommonName, "smallstep test") - assert.Equals(t, crt.DNSNames, []string{"test.smallstep.com"}) + assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.DNSNames, []string{"test.smallstep.com"}) return nil }, } @@ -901,10 +908,10 @@ ZYtQ9Ot36qc= "sans": []string{"test.smallstep.com"}, "cnf": map[string]any{"kid": fingerprint}, }) - assert.FatalError(t, err) + require.NoError(t, err) opts, err := auth.Authorize(ctx, tok) - assert.FatalError(t, err) + require.NoError(t, err) return &signTest{ auth: auth, @@ -922,31 +929,31 @@ ZYtQ9Ot36qc= t.Run(name, func(t *testing.T) { tc := genTestCase(t) - certChain, err := tc.auth.Sign(tc.csr, tc.signOpts, tc.extraOpts...) + certChain, err := tc.auth.SignWithContext(context.Background(), tc.csr, tc.signOpts, tc.extraOpts...) if err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) var sc render.StatusCodedError - assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) - assert.HasPrefix(t, err.Error(), tc.err.Error()) + require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") + assert.Equal(t, sc.StatusCode(), tc.code) + assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error - assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equals(t, ctxErr.Details["csr"], tc.csr) - assert.Equals(t, ctxErr.Details["signOptions"], tc.signOpts) + 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) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - assert.Equals(t, leaf.NotBefore, tc.notBefore) - assert.Equals(t, leaf.NotAfter, tc.notAfter) + assert.Equal(t, leaf.NotBefore, tc.notBefore) + assert.Equal(t, leaf.NotAfter, tc.notAfter) tmplt := a.config.AuthorityConfig.Template if tc.csr.Subject.CommonName == "" { - assert.Equals(t, leaf.Subject, pkix.Name{}) + assert.Equal(t, leaf.Subject, pkix.Name{}) } else { - assert.Equals(t, leaf.Subject.String(), + assert.Equal(t, leaf.Subject.String(), pkix.Name{ Country: []string{tmplt.Country}, Organization: []string{tmplt.Organization}, @@ -955,18 +962,18 @@ ZYtQ9Ot36qc= Province: []string{tmplt.Province}, CommonName: "smallstep test", }.String()) - assert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"}) + assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com"}) } - assert.Equals(t, leaf.Issuer, intermediate.Subject) - assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - assert.Equals(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) + 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}) issuer := getDefaultIssuer(a) subjectKeyID, err := generateSubjectKeyID(pub) - assert.FatalError(t, err) - assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) - assert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + require.NoError(t, err) + assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) + assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) // Verify Provisioner OID found := 0 @@ -976,18 +983,18 @@ ZYtQ9Ot36qc= found++ val := stepProvisionerASN1{} _, err := asn1.Unmarshal(ext.Value, &val) - assert.FatalError(t, err) - assert.Equals(t, val.Type, provisionerTypeJWK) - assert.Equals(t, val.Name, []byte(p.Name)) - assert.Equals(t, val.CredentialID, []byte(p.Key.KeyID)) + 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)) // Basic Constraints case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 19})): val := basicConstraints{} _, err := asn1.Unmarshal(ext.Value, &val) - assert.FatalError(t, err) + require.NoError(t, err) assert.False(t, val.IsCA, false) - assert.Equals(t, val.MaxPathLen, 0) + assert.Equal(t, val.MaxPathLen, 0) // SAN extension case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 17})): @@ -998,11 +1005,11 @@ ZYtQ9Ot36qc= } } } - assert.Equals(t, found, 1) + assert.Equal(t, found, 1) realIntermediate, err := x509.ParseCertificate(issuer.Raw) - assert.FatalError(t, err) - assert.Equals(t, intermediate, realIntermediate) - assert.Len(t, tc.extensionsCount, leaf.Extensions) + require.NoError(t, err) + assert.Equal(t, intermediate, realIntermediate) + assert.Len(t, leaf.Extensions, tc.extensionsCount) } } }) @@ -1132,7 +1139,7 @@ func TestAuthority_Renew(t *testing.T) { for name, genTestCase := range tests { t.Run(name, func(t *testing.T) { tc, err := genTestCase() - assert.FatalError(t, err) + require.NoError(t, err) var certChain []*x509.Certificate if tc.auth != nil { @@ -1144,19 +1151,19 @@ func TestAuthority_Renew(t *testing.T) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) var sc render.StatusCodedError - assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) - assert.HasPrefix(t, err.Error(), tc.err.Error()) + require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") + assert.Equal(t, sc.StatusCode(), tc.code) + assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error - assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) + require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") + assert.Equal(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - assert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) + assert.Equal(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) @@ -1166,30 +1173,30 @@ func TestAuthority_Renew(t *testing.T) { assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) tmplt := a.config.AuthorityConfig.Template - assert.Equals(t, leaf.RawSubject, tc.cert.RawSubject) - assert.Equals(t, leaf.Subject.Country, []string{tmplt.Country}) - assert.Equals(t, leaf.Subject.Organization, []string{tmplt.Organization}) - assert.Equals(t, leaf.Subject.Locality, []string{tmplt.Locality}) - assert.Equals(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress}) - assert.Equals(t, leaf.Subject.Province, []string{tmplt.Province}) - assert.Equals(t, leaf.Subject.CommonName, tmplt.CommonName) - - assert.Equals(t, leaf.Issuer, intermediate.Subject) - - assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - assert.Equals(t, leaf.ExtKeyUsage, + 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, 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.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) + assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey) - assert.FatalError(t, err) - assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) + require.NoError(t, err) + assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) // We did not change the intermediate before renewing. authIssuer := getDefaultIssuer(tc.auth) if issuer.SerialNumber == authIssuer.SerialNumber { - assert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1209,7 +1216,7 @@ func TestAuthority_Renew(t *testing.T) { } } else { // We did change the intermediate before renewing. - assert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) + assert.Equal(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1237,8 +1244,8 @@ func TestAuthority_Renew(t *testing.T) { } realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) - assert.FatalError(t, err) - assert.Equals(t, intermediate, realIntermediate) + require.NoError(t, err) + assert.Equal(t, intermediate, realIntermediate) } } }) @@ -1247,7 +1254,7 @@ func TestAuthority_Renew(t *testing.T) { func TestAuthority_Rekey(t *testing.T) { pub, _, err := keyutil.GenerateDefaultKeyPair() - assert.FatalError(t, err) + require.NoError(t, err) a := testAuthority(t) a.config.AuthorityConfig.Template = &ASN1DN{ @@ -1337,7 +1344,7 @@ func TestAuthority_Rekey(t *testing.T) { for name, genTestCase := range tests { t.Run(name, func(t *testing.T) { tc, err := genTestCase() - assert.FatalError(t, err) + require.NoError(t, err) var certChain []*x509.Certificate if tc.auth != nil { @@ -1349,19 +1356,19 @@ func TestAuthority_Rekey(t *testing.T) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) var sc render.StatusCodedError - assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) - assert.HasPrefix(t, err.Error(), tc.err.Error()) + require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") + assert.Equal(t, sc.StatusCode(), tc.code) + assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error - assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) + require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") + assert.Equal(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - assert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) + assert.Equal(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) @@ -1371,7 +1378,7 @@ func TestAuthority_Rekey(t *testing.T) { assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) tmplt := a.config.AuthorityConfig.Template - assert.Equals(t, leaf.Subject.String(), + assert.Equal(t, leaf.Subject.String(), pkix.Name{ Country: []string{tmplt.Country}, Organization: []string{tmplt.Organization}, @@ -1380,32 +1387,32 @@ func TestAuthority_Rekey(t *testing.T) { Province: []string{tmplt.Province}, CommonName: tmplt.CommonName, }.String()) - assert.Equals(t, leaf.Issuer, intermediate.Subject) + assert.Equal(t, leaf.Issuer, intermediate.Subject) - assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - assert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - assert.Equals(t, leaf.ExtKeyUsage, + 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.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) + assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) // Test Public Key and SubjectKeyId expectedPK := tc.pk if tc.pk == nil { expectedPK = cert.PublicKey } - assert.Equals(t, leaf.PublicKey, expectedPK) + assert.Equal(t, leaf.PublicKey, expectedPK) subjectKeyID, err := generateSubjectKeyID(expectedPK) - assert.FatalError(t, err) - assert.Equals(t, leaf.SubjectKeyId, subjectKeyID) + require.NoError(t, err) + assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) if tc.pk == nil { - assert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId) + assert.Equal(t, leaf.SubjectKeyId, cert.SubjectKeyId) } // We did not change the intermediate before renewing. authIssuer := getDefaultIssuer(tc.auth) if issuer.SerialNumber == authIssuer.SerialNumber { - assert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1425,7 +1432,7 @@ func TestAuthority_Rekey(t *testing.T) { } } else { // We did change the intermediate before renewing. - assert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) + assert.Equal(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1453,8 +1460,8 @@ func TestAuthority_Rekey(t *testing.T) { } realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) - assert.FatalError(t, err) - assert.Equals(t, intermediate, realIntermediate) + require.NoError(t, err) + assert.Equal(t, intermediate, realIntermediate) } } }) @@ -1489,10 +1496,10 @@ func TestAuthority_GetTLSOptions(t *testing.T) { for name, genTestCase := range tests { t.Run(name, func(t *testing.T) { tc, err := genTestCase() - assert.FatalError(t, err) + require.NoError(t, err) opts := tc.auth.GetTLSOptions() - assert.Equals(t, opts, tc.opts) + assert.Equal(t, opts, tc.opts) }) } } @@ -1505,11 +1512,11 @@ func TestAuthority_Revoke(t *testing.T) { now := time.Now().UTC() jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass"))) - assert.FatalError(t, err) + require.NoError(t, err) sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, (&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID)) - assert.FatalError(t, err) + require.NoError(t, err) a := testAuthority(t) @@ -1548,7 +1555,7 @@ func TestAuthority_Revoke(t *testing.T) { ID: "44", } raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() - assert.FatalError(t, err) + require.NoError(t, err) return test{ auth: a, @@ -1562,9 +1569,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.Equals(t, err.Details["token"], raw) - assert.Equals(t, err.Details["tokenID"], "44") - assert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, err.Details["token"], raw) + assert.Equal(t, err.Details["tokenID"], "44") + assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") }, } }, @@ -1588,7 +1595,7 @@ func TestAuthority_Revoke(t *testing.T) { ID: "44", } raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() - assert.FatalError(t, err) + require.NoError(t, err) return test{ auth: _a, @@ -1602,9 +1609,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("authority.Revoke: force"), code: http.StatusInternalServerError, checkErrDetails: func(err *errs.Error) { - assert.Equals(t, err.Details["token"], raw) - assert.Equals(t, err.Details["tokenID"], "44") - assert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, err.Details["token"], raw) + assert.Equal(t, err.Details["tokenID"], "44") + assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") }, } }, @@ -1628,7 +1635,7 @@ func TestAuthority_Revoke(t *testing.T) { ID: "44", } raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() - assert.FatalError(t, err) + require.NoError(t, err) return test{ auth: _a, @@ -1642,9 +1649,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.Equals(t, err.Details["token"], raw) - assert.Equals(t, err.Details["tokenID"], "44") - assert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, err.Details["token"], raw) + assert.Equal(t, err.Details["tokenID"], "44") + assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") }, } }, @@ -1667,7 +1674,7 @@ func TestAuthority_Revoke(t *testing.T) { ID: "44", } raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() - assert.FatalError(t, err) + require.NoError(t, err) return test{ auth: _a, ctx: tlsRevokeCtx, @@ -1683,7 +1690,7 @@ func TestAuthority_Revoke(t *testing.T) { _a := testAuthority(t, WithDatabase(&db.MockAuthDB{})) crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt") - assert.FatalError(t, err) + require.NoError(t, err) return test{ auth: _a, @@ -1701,7 +1708,7 @@ func TestAuthority_Revoke(t *testing.T) { _a := testAuthority(t, WithDatabase(&db.MockAuthDB{})) crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt") - assert.FatalError(t, err) + require.NoError(t, err) // Filter out provisioner extension. for i, ext := range crt.Extensions { if ext.Id.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1}) { @@ -1726,7 +1733,7 @@ func TestAuthority_Revoke(t *testing.T) { _a := testAuthority(t, WithDatabase(&db.MockAuthDB{})) crt, err := pemutil.ReadCertificate("./testdata/certs/foo.crt") - assert.FatalError(t, err) + require.NoError(t, err) return test{ auth: _a, @@ -1759,7 +1766,7 @@ func TestAuthority_Revoke(t *testing.T) { ID: "44", } raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() - assert.FatalError(t, err) + require.NoError(t, err) return test{ auth: a, ctx: provisioner.NewContextWithMethod(context.Background(), provisioner.SSHRevokeMethod), @@ -1778,17 +1785,17 @@ func TestAuthority_Revoke(t *testing.T) { if err := tc.auth.Revoke(tc.ctx, tc.opts); err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { var sc render.StatusCodedError - assert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equals(t, sc.StatusCode(), tc.code) - assert.HasPrefix(t, err.Error(), tc.err.Error()) + require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") + assert.Equal(t, sc.StatusCode(), tc.code) + assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error - assert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equals(t, ctxErr.Details["serialNumber"], tc.opts.Serial) - assert.Equals(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode) - assert.Equals(t, ctxErr.Details["reason"], tc.opts.Reason) - assert.Equals(t, ctxErr.Details["MTLS"], tc.opts.MTLS) - assert.Equals(t, ctxErr.Details["context"], provisioner.RevokeMethod.String()) + 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()) if tc.checkErrDetails != nil { tc.checkErrDetails(ctxErr) @@ -1871,9 +1878,9 @@ func TestAuthority_constraints(t *testing.T) { t.Fatal(err) } - _, err = auth.Sign(csr, provisioner.SignOptions{}, templateOption) + _, err = auth.SignWithContext(context.Background(), csr, provisioner.SignOptions{}, templateOption) if (err != nil) != tt.wantErr { - t.Errorf("Authority.Sign() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("Authority.SignWithContext() error = %v, wantErr %v", err, tt.wantErr) } _, err = auth.Renew(cert) @@ -1890,13 +1897,11 @@ func TestAuthority_CRL(t *testing.T) { validIssuer := "step-cli" validAudience := testAudiences.Revoke now := time.Now().UTC() - // jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass"))) - assert.FatalError(t, err) - // + require.NoError(t, err) sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, (&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID)) - assert.FatalError(t, err) + require.NoError(t, err) crlCtx := provisioner.NewContextWithMethod(context.Background(), provisioner.RevokeMethod) @@ -1941,7 +1946,7 @@ func TestAuthority_CRL(t *testing.T) { auth: a, ctx: crlCtx, expected: nil, - err: database.ErrNotFound, + err: errors.New("authority.GetCertificateRevocationList: not found"), } }, "ok/crl-full": func() test { @@ -1986,7 +1991,7 @@ func TestAuthority_CRL(t *testing.T) { ID: sn, } raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() - assert.FatalError(t, err) + require.NoError(t, err) err = a.Revoke(crlCtx, &RevokeOptions{ Serial: sn, ReasonCode: reasonCode, @@ -1994,7 +1999,7 @@ func TestAuthority_CRL(t *testing.T) { OTT: raw, }) - assert.FatalError(t, err) + require.NoError(t, err) ex = append(ex, sn) } @@ -2009,22 +2014,22 @@ func TestAuthority_CRL(t *testing.T) { for name, f := range tests { tc := f() t.Run(name, func(t *testing.T) { - if crlBytes, err := tc.auth.GetCertificateRevocationList(); err == nil { - crl, parseErr := x509.ParseRevocationList(crlBytes) - if parseErr != nil { - t.Errorf("x509.ParseCertificateRequest() error = %v, wantErr %v", parseErr, nil) - return - } + crlInfo, err := tc.auth.GetCertificateRevocationList() + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + assert.Nil(t, crlInfo) + return + } - var cmpList []string - for _, c := range crl.RevokedCertificates { - cmpList = append(cmpList, c.SerialNumber.String()) - } + crl, parseErr := x509.ParseRevocationList(crlInfo.Data) + require.NoError(t, parseErr) - assert.Equals(t, cmpList, tc.expected) - } else { - assert.NotNil(t, tc.err, err.Error()) + var cmpList []string + for _, c := range crl.RevokedCertificateEntries { + cmpList = append(cmpList, c.SerialNumber.String()) } + + assert.Equal(t, tc.expected, cmpList) }) } } diff --git a/authority/webhook.go b/authority/webhook.go index d887e077..29e3e6c3 100644 --- a/authority/webhook.go +++ b/authority/webhook.go @@ -1,8 +1,12 @@ package authority -import "github.com/smallstep/certificates/webhook" +import ( + "context" + + "github.com/smallstep/certificates/webhook" +) type webhookController interface { - Enrich(*webhook.RequestBody) error - Authorize(*webhook.RequestBody) error + Enrich(context.Context, *webhook.RequestBody) error + Authorize(context.Context, *webhook.RequestBody) error } diff --git a/authority/webhook_test.go b/authority/webhook_test.go index 0e713af7..75b59f63 100644 --- a/authority/webhook_test.go +++ b/authority/webhook_test.go @@ -1,6 +1,8 @@ package authority import ( + "context" + "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/webhook" ) @@ -14,7 +16,7 @@ type mockWebhookController struct { var _ webhookController = &mockWebhookController{} -func (wc *mockWebhookController) Enrich(*webhook.RequestBody) error { +func (wc *mockWebhookController) Enrich(context.Context, *webhook.RequestBody) error { for key, data := range wc.respData { wc.templateData.SetWebhook(key, data) } @@ -22,6 +24,6 @@ func (wc *mockWebhookController) Enrich(*webhook.RequestBody) error { return wc.enrichErr } -func (wc *mockWebhookController) Authorize(*webhook.RequestBody) error { +func (wc *mockWebhookController) Authorize(context.Context, *webhook.RequestBody) error { return wc.authorizeErr } diff --git a/ca/acmeClient.go b/ca/acmeClient.go index bb3b1d84..3ef2f191 100644 --- a/ca/acmeClient.go +++ b/ca/acmeClient.go @@ -48,6 +48,7 @@ func NewACMEClient(endpoint string, contact []string, opts ...ClientOption) (*AC return nil, errors.Wrapf(err, "creating GET request %s failed", endpoint) } req.Header.Set("User-Agent", UserAgent) + enforceRequestID(req) resp, err := ac.client.Do(req) if err != nil { return nil, errors.Wrapf(err, "client GET %s failed", endpoint) @@ -109,6 +110,7 @@ func (c *ACMEClient) GetNonce() (string, error) { return "", errors.Wrapf(err, "creating GET request %s failed", c.dir.NewNonce) } req.Header.Set("User-Agent", UserAgent) + enforceRequestID(req) resp, err := c.client.Do(req) if err != nil { return "", errors.Wrapf(err, "client GET %s failed", c.dir.NewNonce) @@ -188,6 +190,7 @@ func (c *ACMEClient) post(payload []byte, url string, headerOps ...withHeaderOpt } req.Header.Set("Content-Type", "application/jose+json") req.Header.Set("User-Agent", UserAgent) + enforceRequestID(req) resp, err := c.client.Do(req) if err != nil { return nil, errors.Wrapf(err, "client POST %s failed", c.dir.NewOrder) diff --git a/ca/adminClient.go b/ca/adminClient.go index 18221146..3ead6629 100644 --- a/ca/adminClient.go +++ b/ca/adminClient.go @@ -204,7 +204,7 @@ func (o *adminOptions) apply(opts []AdminOption) (err error) { func (o *adminOptions) rawQuery() string { v := url.Values{} - if len(o.cursor) > 0 { + if o.cursor != "" { v.Set("cursor", o.cursor) } if o.limit > 0 { diff --git a/ca/ca.go b/ca/ca.go index 7baf2419..0b426ded 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -26,8 +26,11 @@ import ( "github.com/smallstep/certificates/authority/admin" adminAPI "github.com/smallstep/certificates/authority/admin/api" "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" + "github.com/smallstep/certificates/internal/metrix" "github.com/smallstep/certificates/logging" + "github.com/smallstep/certificates/middleware/requestid" "github.com/smallstep/certificates/monitoring" "github.com/smallstep/certificates/scep" scepAPI "github.com/smallstep/certificates/scep/api" @@ -46,6 +49,8 @@ type options struct { sshHostPassword []byte sshUserPassword []byte database db.AuthDB + x509CAService apiv1.CertificateAuthorityService + tlsConfig *tls.Config } func (o *options) apply(opts []Option) { @@ -65,6 +70,13 @@ func WithConfigFile(name string) Option { } } +// WithX509CAService provides the x509CAService to be used for signing x509 requests +func WithX509CAService(svc apiv1.CertificateAuthorityService) Option { + return func(o *options) { + o.x509CAService = svc + } +} + // WithPassword sets the given password as the configured password in the CA // options. func WithPassword(password []byte) Option { @@ -104,6 +116,14 @@ func WithDatabase(d db.AuthDB) Option { } } +// WithTLSConfig sets the TLS configuration to be used by the HTTP(s) server +// spun by step-ca. +func WithTLSConfig(t *tls.Config) Option { + return func(o *options) { + o.tlsConfig = t + } +} + // WithLinkedCAToken sets the token used to authenticate with the linkedca. func WithLinkedCAToken(token string) Option { return func(o *options) { @@ -125,6 +145,7 @@ type CA struct { config *config.Config srv *server.Server insecureSrv *server.Server + metricsSrv *server.Server opts *options renewer *TLSRenewer compactStop chan struct{} @@ -163,6 +184,16 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { opts = append(opts, authority.WithQuietInit()) } + if ca.opts.x509CAService != nil { + opts = append(opts, authority.WithX509CAService(ca.opts.x509CAService)) + } + + var meter *metrix.Meter + if ca.config.MetricsAddress != "" { + meter = metrix.New() + opts = append(opts, authority.WithMeter(meter)) + } + webhookTransport := http.DefaultTransport.(*http.Transport).Clone() opts = append(opts, authority.WithWebhookClient(&http.Client{Transport: webhookTransport})) @@ -172,9 +203,20 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { } ca.auth = auth - tlsConfig, clientTLSConfig, err := ca.getTLSConfig(auth) - if err != nil { - return nil, err + var tlsConfig *tls.Config + var clientTLSConfig *tls.Config + if ca.opts.tlsConfig != nil { + // try using the tls Configuration supplied by the caller + log.Print("Using tls configuration supplied by the application") + tlsConfig = ca.opts.tlsConfig + clientTLSConfig = ca.opts.tlsConfig + } else { + // default to using the step-ca x509 Signer Interface + log.Print("Building new tls configuration using step-ca x509 Signer Interface") + tlsConfig, clientTLSConfig, err = ca.getTLSConfig(auth) + if err != nil { + return nil, err + } } webhookTransport.TLSClientConfig = clientTLSConfig @@ -288,15 +330,21 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { } // Add logger if configured + var legacyTraceHeader string if len(cfg.Logger) > 0 { logger, err := logging.New("ca", cfg.Logger) if err != nil { return nil, err } + legacyTraceHeader = logger.GetTraceHeader() handler = logger.Middleware(handler) insecureHandler = logger.Middleware(insecureHandler) } + // always use request ID middleware; traceHeader is provided for backwards compatibility (for now) + handler = requestid.New(legacyTraceHeader).Middleware(handler) + insecureHandler = requestid.New(legacyTraceHeader).Middleware(insecureHandler) + // Create context with all the necessary values. baseContext := buildContext(auth, scepAuthority, acmeDB, acmeLinker) @@ -318,6 +366,13 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { } } + if meter != nil { + ca.metricsSrv = server.New(ca.config.MetricsAddress, meter, nil) + ca.metricsSrv.BaseContext = func(net.Listener) context.Context { + return baseContext + } + } + return ca, nil } @@ -404,6 +459,14 @@ func (ca *CA) Run() error { }() } + if ca.metricsSrv != nil { + wg.Add(1) + go func() { + defer wg.Done() + errs <- ca.metricsSrv.ListenAndServe() + }() + } + wg.Add(1) go func() { defer wg.Done() @@ -413,6 +476,20 @@ func (ca *CA) Run() error { // wait till error occurs; ensures the servers keep listening err := <-errs + // if the error is not the usual HTTP server closed error, it is + // highly likely that an error occurred when starting one of the + // CA servers, possibly because of a port already being in use or + // some part of the configuration not being correct. This case is + // handled by stopping the CA in its entirety. + if !errors.Is(err, http.ErrServerClosed) { + log.Println("shutting down due to startup error ...") + if stopErr := ca.Stop(); stopErr != nil { + err = fmt.Errorf("failed stopping CA after error occurred: %w: %w", err, stopErr) + } else { + err = fmt.Errorf("stopped CA after error occurred: %w", err) + } + } + wg.Wait() return err @@ -421,7 +498,10 @@ func (ca *CA) Run() error { // Stop stops the CA calling to the server Shutdown method. func (ca *CA) Stop() error { close(ca.compactStop) - ca.renewer.Stop() + if ca.renewer != nil { + ca.renewer.Stop() + } + if err := ca.auth.Shutdown(); err != nil { log.Printf("error stopping ca.Authority: %+v\n", err) } @@ -480,6 +560,13 @@ func (ca *CA) Reload() error { } } + if ca.metricsSrv != nil { + if err = ca.metricsSrv.Reload(newCA.metricsSrv); err != nil { + logContinue("Reload failed because metrics server could not be replaced.") + return errors.Wrap(err, "error reloading metrics server") + } + } + if err = ca.srv.Reload(newCA.srv); err != nil { logContinue("Reload failed because server could not be replaced.") return errors.Wrap(err, "error reloading server") @@ -489,7 +576,10 @@ func (ca *CA) Reload() error { // 2. Safely shutdown any internal resources (e.g. key manager) // 3. Replace ca properties // Do not replace ca.srv - ca.renewer.Stop() + if ca.renewer != nil { + ca.renewer.Stop() + } + ca.auth.CloseForReload() ca.auth = newCA.auth ca.config = newCA.config @@ -588,7 +678,7 @@ func (ca *CA) shouldServeSCEPEndpoints() bool { //nolint:unused // useful for debugging func dumpRoutes(mux chi.Routes) { // helpful routine for logging all routes - walkFunc := func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { + walkFunc := func(method string, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error { fmt.Printf("%s %s\n", method, route) return nil } diff --git a/ca/ca_test.go b/ca/ca_test.go index 7ad25cc6..a8c173c4 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -289,6 +289,9 @@ ZEp7knvU2psWRw== if assert.Equals(t, rr.Code, tc.status) { body := &ClosingBuffer{rr.Body} + resp := &http.Response{ + Body: body, + } if rr.Code < http.StatusBadRequest { var sign api.SignResponse assert.FatalError(t, readJSON(body, &sign)) @@ -325,7 +328,7 @@ ZEp7knvU2psWRw== assert.FatalError(t, err) assert.Equals(t, intermediate, realIntermediate) } else { - err := readError(body) + err := readError(resp) if tc.errMsg == "" { assert.FatalError(t, errors.New("must validate response error")) } @@ -369,6 +372,9 @@ func TestCAProvisioners(t *testing.T) { if assert.Equals(t, rr.Code, tc.status) { body := &ClosingBuffer{rr.Body} + resp := &http.Response{ + Body: body, + } if rr.Code < http.StatusBadRequest { var resp api.ProvisionersResponse @@ -379,7 +385,7 @@ func TestCAProvisioners(t *testing.T) { assert.FatalError(t, err) assert.Equals(t, a, b) } else { - err := readError(body) + err := readError(resp) if tc.errMsg == "" { assert.FatalError(t, errors.New("must validate response error")) } @@ -436,12 +442,15 @@ func TestCAProvisionerEncryptedKey(t *testing.T) { if assert.Equals(t, rr.Code, tc.status) { body := &ClosingBuffer{rr.Body} + resp := &http.Response{ + Body: body, + } if rr.Code < http.StatusBadRequest { var ek api.ProvisionerKeyResponse assert.FatalError(t, readJSON(body, &ek)) assert.Equals(t, ek.Key, tc.expectedKey) } else { - err := readError(body) + err := readError(resp) if tc.errMsg == "" { assert.FatalError(t, errors.New("must validate response error")) } @@ -498,12 +507,15 @@ func TestCARoot(t *testing.T) { if assert.Equals(t, rr.Code, tc.status) { body := &ClosingBuffer{rr.Body} + resp := &http.Response{ + Body: body, + } if rr.Code < http.StatusBadRequest { var root api.RootResponse assert.FatalError(t, readJSON(body, &root)) assert.Equals(t, root.RootPEM.Certificate, rootCrt) } else { - err := readError(body) + err := readError(resp) if tc.errMsg == "" { assert.FatalError(t, errors.New("must validate response error")) } @@ -641,6 +653,9 @@ func TestCARenew(t *testing.T) { if assert.Equals(t, rr.Code, tc.status) { body := &ClosingBuffer{rr.Body} + resp := &http.Response{ + Body: body, + } if rr.Code < http.StatusBadRequest { var sign api.SignResponse assert.FatalError(t, readJSON(body, &sign)) @@ -673,7 +688,7 @@ func TestCARenew(t *testing.T) { assert.Equals(t, *sign.TLSOptions, authority.DefaultTLSOptions) } else { - err := readError(body) + err := readError(resp) if tc.errMsg == "" { assert.FatalError(t, errors.New("must validate response error")) } diff --git a/ca/client.go b/ca/client.go index ac13e1fe..b18efbaf 100644 --- a/ca/client.go +++ b/ca/client.go @@ -27,12 +27,14 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca/client" "github.com/smallstep/certificates/ca/identity" "github.com/smallstep/certificates/errs" "go.step.sm/cli-utils/step" "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/pemutil" + "go.step.sm/crypto/randutil" "go.step.sm/crypto/x509util" "golang.org/x/net/http2" "google.golang.org/protobuf/encoding/protojson" @@ -83,8 +85,7 @@ func (c *uaClient) GetWithContext(ctx context.Context, u string) (*http.Response if err != nil { return nil, errors.Wrapf(err, "create GET %s request failed", u) } - req.Header.Set("User-Agent", UserAgent) - return c.Client.Do(req) + return c.Do(req) } func (c *uaClient) Post(u, contentType string, body io.Reader) (*http.Response, error) { @@ -97,12 +98,43 @@ func (c *uaClient) PostWithContext(ctx context.Context, u, contentType string, b return nil, errors.Wrapf(err, "create POST %s request failed", u) } req.Header.Set("Content-Type", contentType) - req.Header.Set("User-Agent", UserAgent) - return c.Client.Do(req) + return c.Do(req) +} + +// requestIDHeader is the header name used for propagating request IDs from +// the CA client to the CA and back again. +const requestIDHeader = "X-Request-Id" + +// newRequestID generates a new random UUIDv4 request ID. If it fails, +// the request ID will be the empty string. +func newRequestID() string { + requestID, err := randutil.UUIDv4() + if err != nil { + return "" + } + + return requestID +} + +// enforceRequestID checks if the X-Request-Id HTTP header is filled. If it's +// empty, the context is searched for a request ID. If that's also empty, a new +// request ID is generated. +func enforceRequestID(r *http.Request) { + if requestID := r.Header.Get(requestIDHeader); requestID == "" { + if reqID, ok := client.RequestIDFromContext(r.Context()); ok { + // TODO(hs): ensure the request ID from the context is fresh, and thus hasn't been + // used before by the client (unless it's a retry for the same request)? + requestID = reqID + } else { + requestID = newRequestID() + } + r.Header.Set(requestIDHeader, requestID) + } } func (c *uaClient) Do(req *http.Request) (*http.Response, error) { req.Header.Set("User-Agent", UserAgent) + enforceRequestID(req) return c.Client.Do(req) } @@ -375,8 +407,8 @@ func getTransportFromSHA256(endpoint, sum string) (http.RoundTripper, error) { if err != nil { return nil, err } - client := &Client{endpoint: u} - root, err := client.Root(sum) + caClient := &Client{endpoint: u} + root, err := caClient.Root(sum) if err != nil { return nil, err } @@ -610,7 +642,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var version api.VersionResponse if err := readJSON(resp.Body, &version); err != nil { @@ -640,7 +672,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var health api.HealthResponse if err := readJSON(resp.Body, &health); err != nil { @@ -675,7 +707,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var root api.RootResponse if err := readJSON(resp.Body, &root); err != nil { @@ -714,7 +746,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var sign api.SignResponse if err := readJSON(resp.Body, &sign); err != nil { @@ -737,14 +769,14 @@ func (c *Client) Renew(tr http.RoundTripper) (*api.SignResponse, error) { func (c *Client) RenewWithContext(ctx context.Context, tr http.RoundTripper) (*api.SignResponse, error) { var retried bool u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"}) - client := &http.Client{Transport: tr} + httpClient := &http.Client{Transport: tr} retry: req, err := http.NewRequestWithContext(ctx, "POST", u.String(), http.NoBody) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") - resp, err := client.Do(req) + resp, err := httpClient.Do(req) if err != nil { return nil, clientError(err) } @@ -753,7 +785,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var sign api.SignResponse if err := readJSON(resp.Body, &sign); err != nil { @@ -790,7 +822,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var sign api.SignResponse if err := readJSON(resp.Body, &sign); err != nil { @@ -814,14 +846,14 @@ func (c *Client) RekeyWithContext(ctx context.Context, req *api.RekeyRequest, tr return nil, errors.Wrap(err, "error marshaling request") } u := c.endpoint.ResolveReference(&url.URL{Path: "/rekey"}) - client := &http.Client{Transport: tr} + httpClient := &http.Client{Transport: tr} retry: httpReq, err := http.NewRequestWithContext(ctx, "POST", u.String(), bytes.NewReader(body)) if err != nil { return nil, err } httpReq.Header.Set("Content-Type", "application/json") - resp, err := client.Do(httpReq) + resp, err := httpClient.Do(httpReq) if err != nil { return nil, clientError(err) } @@ -830,7 +862,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var sign api.SignResponse if err := readJSON(resp.Body, &sign); err != nil { @@ -853,16 +885,16 @@ func (c *Client) RevokeWithContext(ctx context.Context, req *api.RevokeRequest, if err != nil { return nil, errors.Wrap(err, "error marshaling request") } - var client *uaClient + var uaClient *uaClient retry: if tr != nil { - client = newClient(tr) + uaClient = newClient(tr) } else { - client = c.client + uaClient = c.client } u := c.endpoint.ResolveReference(&url.URL{Path: "/revoke"}) - resp, err := client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body)) + resp, err := uaClient.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body)) if err != nil { return nil, clientError(err) } @@ -871,7 +903,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var revoke api.RevokeResponse if err := readJSON(resp.Body, &revoke); err != nil { @@ -914,7 +946,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var provisioners api.ProvisionersResponse if err := readJSON(resp.Body, &provisioners); err != nil { @@ -946,7 +978,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var key api.ProvisionerKeyResponse if err := readJSON(resp.Body, &key); err != nil { @@ -976,7 +1008,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var roots api.RootsResponse if err := readJSON(resp.Body, &roots); err != nil { @@ -1006,7 +1038,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var federation api.FederationResponse if err := readJSON(resp.Body, &federation); err != nil { @@ -1040,7 +1072,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var sign api.SSHSignResponse if err := readJSON(resp.Body, &sign); err != nil { @@ -1074,7 +1106,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var renew api.SSHRenewResponse if err := readJSON(resp.Body, &renew); err != nil { @@ -1108,7 +1140,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var rekey api.SSHRekeyResponse if err := readJSON(resp.Body, &rekey); err != nil { @@ -1142,7 +1174,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var revoke api.SSHRevokeResponse if err := readJSON(resp.Body, &revoke); err != nil { @@ -1172,7 +1204,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var keys api.SSHRootsResponse if err := readJSON(resp.Body, &keys); err != nil { @@ -1202,7 +1234,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var keys api.SSHRootsResponse if err := readJSON(resp.Body, &keys); err != nil { @@ -1236,7 +1268,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var cfg api.SSHConfigResponse if err := readJSON(resp.Body, &cfg); err != nil { @@ -1275,7 +1307,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var check api.SSHCheckPrincipalResponse if err := readJSON(resp.Body, &check); err != nil { @@ -1304,7 +1336,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var hosts api.SSHGetHostsResponse if err := readJSON(resp.Body, &hosts); err != nil { @@ -1336,7 +1368,7 @@ retry: retried = true goto retry } - return nil, readError(resp.Body) + return nil, readError(resp) } var bastion api.SSHBastionResponse if err := readJSON(resp.Body, &bastion); err != nil { @@ -1504,12 +1536,13 @@ func readProtoJSON(r io.ReadCloser, m proto.Message) error { return protojson.Unmarshal(data, m) } -func readError(r io.ReadCloser) error { - defer r.Close() +func readError(r *http.Response) error { + defer r.Body.Close() apiErr := new(errs.Error) - if err := json.NewDecoder(r).Decode(apiErr); err != nil { - return err + if err := json.NewDecoder(r.Body).Decode(apiErr); err != nil { + return fmt.Errorf("failed decoding CA error response: %w", err) } + apiErr.RequestID = r.Header.Get("X-Request-Id") return apiErr } diff --git a/ca/client/requestid.go b/ca/client/requestid.go new file mode 100644 index 00000000..1fb785eb --- /dev/null +++ b/ca/client/requestid.go @@ -0,0 +1,18 @@ +package client + +import "context" + +type contextKey struct{} + +// NewRequestIDContext returns a new context with the given request ID added to the +// context. +func NewRequestIDContext(ctx context.Context, requestID string) context.Context { + return context.WithValue(ctx, contextKey{}, requestID) +} + +// RequestIDFromContext returns the request ID from the context if it exists. +// and is not empty. +func RequestIDFromContext(ctx context.Context) (string, bool) { + v, ok := ctx.Value(contextKey{}).(string) + return v, ok && v != "" +} diff --git a/ca/client_test.go b/ca/client_test.go index 6292e3ea..44d24c6e 100644 --- a/ca/client_test.go +++ b/ca/client_test.go @@ -9,24 +9,26 @@ import ( "encoding/json" "encoding/pem" "errors" - "fmt" "net/http" "net/http/httptest" "net/url" "reflect" + "strings" "testing" "time" - "go.step.sm/crypto/x509util" - "golang.org/x/crypto/ssh" - - "github.com/smallstep/assert" + "github.com/google/uuid" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api/read" "github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca/client" "github.com/smallstep/certificates/errs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.step.sm/crypto/x509util" + "golang.org/x/crypto/ssh" ) const ( @@ -106,52 +108,49 @@ DCbKzWTW8lqVdp9Kyf7XEhhc2R8C5w== -----END CERTIFICATE REQUEST-----` ) -func mustKey() *ecdsa.PrivateKey { +func mustKey(t *testing.T) *ecdsa.PrivateKey { + t.Helper() priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - panic(err) - } + require.NoError(t, err) return priv } -func parseCertificate(data string) *x509.Certificate { +func parseCertificate(t *testing.T, data string) *x509.Certificate { + t.Helper() block, _ := pem.Decode([]byte(data)) if block == nil { - panic("failed to parse certificate PEM") + require.Fail(t, "failed to parse certificate PEM") + return nil } cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - panic("failed to parse certificate: " + err.Error()) - } + require.NoError(t, err, "failed to parse certificate") return cert } -func parseCertificateRequest(string) *x509.CertificateRequest { +func parseCertificateRequest(t *testing.T, csrPEM string) *x509.CertificateRequest { + t.Helper() block, _ := pem.Decode([]byte(csrPEM)) if block == nil { - panic("failed to parse certificate request PEM") + require.Fail(t, "failed to parse certificate request PEM") + return nil } csr, err := x509.ParseCertificateRequest(block.Bytes) - if err != nil { - panic("failed to parse certificate request: " + err.Error()) - } + require.NoError(t, err, "failed to parse certificate request") return csr } func equalJSON(t *testing.T, a, b interface{}) bool { + t.Helper() if reflect.DeepEqual(a, b) { return true } + ab, err := json.Marshal(a) - if err != nil { - t.Error(err) - return false - } + require.NoError(t, err) + bb, err := json.Marshal(b) - if err != nil { - t.Error(err) - return false - } + require.NoError(t, err) + return bytes.Equal(ab, bb) } @@ -176,32 +175,23 @@ func TestClient_Version(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { render.JSONStatus(w, tt.response, tt.responseCode) }) got, err := c.Version() - if (err != nil) != tt.wantErr { - t.Errorf("Client.Version() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + assert.EqualError(t, err, tt.expectedErr.Error()) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Version() = %v, want nil", got) - } - assert.HasPrefix(t, tt.expectedErr.Error(), err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Version() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } @@ -226,40 +216,30 @@ func TestClient_Health(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { render.JSONStatus(w, tt.response, tt.responseCode) }) got, err := c.Health() - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.Health() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + assert.EqualError(t, err, tt.expectedErr.Error()) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Health() = %v, want nil", got) - } - assert.HasPrefix(t, tt.expectedErr.Error(), err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Health() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } func TestClient_Root(t *testing.T) { ok := &api.RootResponse{ - RootPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + RootPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)}, } tests := []struct { @@ -280,10 +260,7 @@ func TestClient_Root(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { expected := "/root/" + tt.shasum @@ -294,37 +271,31 @@ func TestClient_Root(t *testing.T) { }) got, err := c.Root(tt.shasum) - if (err != nil) != tt.wantErr { - t.Errorf("Client.Root() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + assert.EqualError(t, err, tt.expectedErr.Error()) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Root() = %v, want nil", got) - } - assert.HasPrefix(t, tt.expectedErr.Error(), err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Root() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } func TestClient_Sign(t *testing.T) { ok := &api.SignResponse{ - ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, - CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)}, + CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)}, CertChainPEM: []api.Certificate{ - {Certificate: parseCertificate(certPEM)}, - {Certificate: parseCertificate(rootPEM)}, + {Certificate: parseCertificate(t, certPEM)}, + {Certificate: parseCertificate(t, rootPEM)}, }, } request := &api.SignRequest{ - CsrPEM: api.CertificateRequest{CertificateRequest: parseCertificateRequest(csrPEM)}, + CsrPEM: api.CertificateRequest{CertificateRequest: parseCertificateRequest(t, csrPEM)}, OTT: "the-ott", NotBefore: api.NewTimeDuration(time.Now()), NotAfter: api.NewTimeDuration(time.Now().AddDate(0, 1, 0)), @@ -350,16 +321,13 @@ func TestClient_Sign(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { body := new(api.SignRequest) if err := read.JSON(req.Body, body); err != nil { e, ok := tt.response.(error) - assert.Fatal(t, ok, "response expected to be error type") + require.True(t, ok, "response expected to be error type") render.Error(w, e) return } else if !equalJSON(t, body, tt.request) { @@ -375,23 +343,16 @@ func TestClient_Sign(t *testing.T) { }) got, err := c.Sign(tt.request) - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.Sign() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + assert.EqualError(t, err, tt.expectedErr.Error()) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Sign() = %v, want nil", got) - } - assert.HasPrefix(t, tt.expectedErr.Error(), err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Sign() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } @@ -422,16 +383,13 @@ func TestClient_Revoke(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { body := new(api.RevokeRequest) if err := read.JSON(req.Body, body); err != nil { e, ok := tt.response.(error) - assert.Fatal(t, ok, "response expected to be error type") + require.True(t, ok, "response expected to be error type") render.Error(w, e) return } else if !equalJSON(t, body, tt.request) { @@ -447,34 +405,27 @@ func TestClient_Revoke(t *testing.T) { }) got, err := c.Revoke(tt.request, nil) - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.Revoke() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + assert.True(t, strings.HasPrefix(err.Error(), tt.expectedErr.Error())) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Revoke() = %v, want nil", got) - } - assert.HasPrefix(t, err.Error(), tt.expectedErr.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Revoke() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } func TestClient_Renew(t *testing.T) { ok := &api.SignResponse{ - ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, - CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)}, + CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)}, CertChainPEM: []api.Certificate{ - {Certificate: parseCertificate(certPEM)}, - {Certificate: parseCertificate(rootPEM)}, + {Certificate: parseCertificate(t, certPEM)}, + {Certificate: parseCertificate(t, rootPEM)}, }, } @@ -497,49 +448,38 @@ func TestClient_Renew(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { render.JSONStatus(w, tt.response, tt.responseCode) }) got, err := c.Renew(nil) - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.Renew() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + var sc render.StatusCodedError + if assert.ErrorAs(t, err, &sc) { + assert.Equal(t, tt.responseCode, sc.StatusCode()) + } + assert.True(t, strings.HasPrefix(err.Error(), tt.err.Error())) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Renew() = %v, want nil", got) - } - - var sc render.StatusCodedError - if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tt.responseCode) - } - assert.HasPrefix(t, err.Error(), tt.err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Renew() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } func TestClient_RenewWithToken(t *testing.T) { ok := &api.SignResponse{ - ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, - CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)}, + CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)}, CertChainPEM: []api.Certificate{ - {Certificate: parseCertificate(certPEM)}, - {Certificate: parseCertificate(rootPEM)}, + {Certificate: parseCertificate(t, certPEM)}, + {Certificate: parseCertificate(t, rootPEM)}, }, } @@ -562,10 +502,7 @@ func TestClient_RenewWithToken(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if req.Header.Get("Authorization") != "Bearer token" { @@ -576,44 +513,36 @@ func TestClient_RenewWithToken(t *testing.T) { }) got, err := c.RenewWithToken("token") - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.RenewWithToken() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + var sc render.StatusCodedError + if assert.ErrorAs(t, err, &sc) { + assert.Equal(t, tt.responseCode, sc.StatusCode()) + } + assert.True(t, strings.HasPrefix(err.Error(), tt.err.Error())) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.RenewWithToken() = %v, want nil", got) - } - - var sc render.StatusCodedError - if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tt.responseCode) - } - assert.HasPrefix(t, err.Error(), tt.err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.RenewWithToken() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } func TestClient_Rekey(t *testing.T) { ok := &api.SignResponse{ - ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, - CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)}, + CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)}, CertChainPEM: []api.Certificate{ - {Certificate: parseCertificate(certPEM)}, - {Certificate: parseCertificate(rootPEM)}, + {Certificate: parseCertificate(t, certPEM)}, + {Certificate: parseCertificate(t, rootPEM)}, }, } request := &api.RekeyRequest{ - CsrPEM: api.CertificateRequest{CertificateRequest: parseCertificateRequest(csrPEM)}, + CsrPEM: api.CertificateRequest{CertificateRequest: parseCertificateRequest(t, csrPEM)}, } tests := []struct { @@ -636,38 +565,27 @@ func TestClient_Rekey(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { render.JSONStatus(w, tt.response, tt.responseCode) }) got, err := c.Rekey(tt.request, nil) - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.Renew() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + var sc render.StatusCodedError + if assert.ErrorAs(t, err, &sc) { + assert.Equal(t, tt.responseCode, sc.StatusCode()) + } + assert.True(t, strings.HasPrefix(err.Error(), tt.err.Error())) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Renew() = %v, want nil", got) - } - - var sc render.StatusCodedError - if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tt.responseCode) - } - assert.HasPrefix(t, err.Error(), tt.err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Renew() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } @@ -699,10 +617,7 @@ func TestClient_Provisioners(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if req.RequestURI != tt.expectedURI { @@ -712,22 +627,16 @@ func TestClient_Provisioners(t *testing.T) { }) got, err := c.Provisioners(tt.args...) - if (err != nil) != tt.wantErr { - t.Errorf("Client.Provisioners() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + assert.True(t, strings.HasPrefix(err.Error(), errs.InternalServerErrorDefaultMsg)) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Provisioners() = %v, want nil", got) - } - assert.HasPrefix(t, errs.InternalServerErrorDefaultMsg, err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Provisioners() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } @@ -755,10 +664,7 @@ func TestClient_ProvisionerKey(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { expected := "/provisioners/" + tt.kid + "/encrypted-key" @@ -769,27 +675,20 @@ func TestClient_ProvisionerKey(t *testing.T) { }) got, err := c.ProvisionerKey(tt.kid) - if (err != nil) != tt.wantErr { - t.Errorf("Client.ProvisionerKey() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + var sc render.StatusCodedError + if assert.ErrorAs(t, err, &sc) { + assert.Equal(t, tt.responseCode, sc.StatusCode()) + } + assert.True(t, strings.HasPrefix(err.Error(), tt.err.Error())) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.ProvisionerKey() = %v, want nil", got) - } - - var sc render.StatusCodedError - if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tt.responseCode) - } - assert.HasPrefix(t, tt.err.Error(), err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.ProvisionerKey() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } @@ -797,7 +696,7 @@ func TestClient_ProvisionerKey(t *testing.T) { func TestClient_Roots(t *testing.T) { ok := &api.RootsResponse{ Certificates: []api.Certificate{ - {Certificate: parseCertificate(rootPEM)}, + {Certificate: parseCertificate(t, rootPEM)}, }, } @@ -819,37 +718,27 @@ func TestClient_Roots(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { render.JSONStatus(w, tt.response, tt.responseCode) }) got, err := c.Roots() - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.Roots() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + var sc render.StatusCodedError + if assert.ErrorAs(t, err, &sc) { + assert.Equal(t, tt.responseCode, sc.StatusCode()) + } + assert.True(t, strings.HasPrefix(err.Error(), tt.err.Error())) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Roots() = %v, want nil", got) - } - var sc render.StatusCodedError - if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tt.responseCode) - } - assert.HasPrefix(t, err.Error(), tt.err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Roots() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } @@ -857,7 +746,7 @@ func TestClient_Roots(t *testing.T) { func TestClient_Federation(t *testing.T) { ok := &api.FederationResponse{ Certificates: []api.Certificate{ - {Certificate: parseCertificate(rootPEM)}, + {Certificate: parseCertificate(t, rootPEM)}, }, } @@ -878,46 +767,34 @@ func TestClient_Federation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { render.JSONStatus(w, tt.response, tt.responseCode) }) got, err := c.Federation() - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.Federation() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + var sc render.StatusCodedError + if assert.ErrorAs(t, err, &sc) { + assert.Equal(t, tt.responseCode, sc.StatusCode()) + } + assert.True(t, strings.HasPrefix(err.Error(), tt.err.Error())) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.Federation() = %v, want nil", got) - } - var sc render.StatusCodedError - if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tt.responseCode) - } - assert.HasPrefix(t, tt.err.Error(), err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.Federation() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } func TestClient_SSHRoots(t *testing.T) { - key, err := ssh.NewPublicKey(mustKey().Public()) - if err != nil { - t.Fatal(err) - } + key, err := ssh.NewPublicKey(mustKey(t).Public()) + require.NoError(t, err) ok := &api.SSHRootsResponse{ HostKeys: []api.SSHPublicKey{{PublicKey: key}}, @@ -941,37 +818,27 @@ func TestClient_SSHRoots(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { render.JSONStatus(w, tt.response, tt.responseCode) }) got, err := c.SSHRoots() - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.SSHKeys() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + if assert.Error(t, err) { + var sc render.StatusCodedError + if assert.ErrorAs(t, err, &sc) { + assert.Equal(t, tt.responseCode, sc.StatusCode()) + } + assert.True(t, strings.HasPrefix(err.Error(), tt.err.Error())) + } + assert.Nil(t, got) return } - switch { - case err != nil: - if got != nil { - t.Errorf("Client.SSHKeys() = %v, want nil", got) - } - var sc render.StatusCodedError - if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tt.responseCode) - } - assert.HasPrefix(t, tt.err.Error(), err.Error()) - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.SSHKeys() = %v, want %v", got, tt.response) - } - } + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } @@ -1003,13 +870,14 @@ func Test_parseEndpoint(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parseEndpoint(tt.args.endpoint) - if (err != nil) != tt.wantErr { - t.Errorf("parseEndpoint() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, got) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("parseEndpoint() = %v, want %v", got, tt.want) - } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) }) } } @@ -1042,24 +910,21 @@ func TestClient_RootFingerprint(t *testing.T) { t.Run(tt.name, func(t *testing.T) { tr := tt.server.Client().Transport c, err := NewClient(tt.server.URL, WithTransport(tr)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) tt.server.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { render.JSONStatus(w, tt.response, tt.responseCode) }) got, err := c.RootFingerprint() - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.RootFingerprint() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + assert.Error(t, err) + assert.Empty(t, got) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Client.RootFingerprint() = %v, want %v", got, tt.want) - } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) }) } } @@ -1068,12 +933,12 @@ func TestClient_RootFingerprintWithServer(t *testing.T) { srv := startCABootstrapServer() defer srv.Close() - client, err := NewClient(srv.URL+"/sign", WithRootFile("testdata/secrets/root_ca.crt")) - assert.FatalError(t, err) + caClient, err := NewClient(srv.URL+"/sign", WithRootFile("testdata/secrets/root_ca.crt")) + require.NoError(t, err) - fp, err := client.RootFingerprint() - assert.FatalError(t, err) - assert.Equals(t, "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7", fp) + fp, err := caClient.RootFingerprint() + assert.NoError(t, err) + assert.Equal(t, "ef742f95dc0d8aa82d3cca4017af6dac3fce84290344159891952d18c53eefe7", fp) } func TestClient_SSHBastion(t *testing.T) { @@ -1103,39 +968,29 @@ func TestClient_SSHBastion(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } + require.NoError(t, err) srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { render.JSONStatus(w, tt.response, tt.responseCode) }) got, err := c.SSHBastion(tt.request) - if (err != nil) != tt.wantErr { - fmt.Printf("%+v", err) - t.Errorf("Client.SSHBastion() error = %v, wantErr %v", err, tt.wantErr) - return - } - - switch { - case err != nil: - if got != nil { - t.Errorf("Client.SSHBastion() = %v, want nil", got) - } - if tt.responseCode != 200 { - var sc render.StatusCodedError - if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tt.responseCode) + if tt.wantErr { + if assert.Error(t, err) { + if tt.responseCode != 200 { + var sc render.StatusCodedError + if assert.ErrorAs(t, err, &sc) { + assert.Equal(t, tt.responseCode, sc.StatusCode()) + } + assert.True(t, strings.HasPrefix(err.Error(), tt.err.Error())) } - assert.HasPrefix(t, err.Error(), tt.err.Error()) - } - default: - if !reflect.DeepEqual(got, tt.response) { - t.Errorf("Client.SSHBastion() = %v, want %v", got, tt.response) } + assert.Nil(t, got) + return } + + assert.NoError(t, err) + assert.Equal(t, tt.response, got) }) } } @@ -1154,13 +1009,60 @@ func TestClient_GetCaURL(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, err := NewClient(tt.caURL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Errorf("NewClient() error = %v", err) - return - } - if got := c.GetCaURL(); got != tt.want { - t.Errorf("Client.GetCaURL() = %v, want %v", got, tt.want) + require.NoError(t, err) + + got := c.GetCaURL() + assert.Equal(t, tt.want, got) + }) + } +} + +func Test_enforceRequestID(t *testing.T) { + set := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + set.Header.Set("X-Request-Id", "already-set") + inContext := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + inContext = inContext.WithContext(client.NewRequestIDContext(inContext.Context(), "from-context")) + newRequestID := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + + tests := []struct { + name string + r *http.Request + want string + }{ + { + name: "set", + r: set, + want: "already-set", + }, + { + name: "context", + r: inContext, + want: "from-context", + }, + { + name: "new", + r: newRequestID, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + enforceRequestID(tt.r) + + v := tt.r.Header.Get("X-Request-Id") + if assert.NotEmpty(t, v) { + if tt.want != "" { + assert.Equal(t, tt.want, v) + } } }) } } + +func Test_newRequestID(t *testing.T) { + requestID := newRequestID() + u, err := uuid.Parse(requestID) + assert.NoError(t, err) + assert.Equal(t, uuid.Version(0x4), u.Version()) + assert.Equal(t, uuid.RFC4122, u.Variant()) + assert.Equal(t, requestID, u.String()) +} diff --git a/ca/provisioner_test.go b/ca/provisioner_test.go index 39193f3f..5a754f08 100644 --- a/ca/provisioner_test.go +++ b/ca/provisioner_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "go.step.sm/crypto/jose" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" @@ -41,14 +43,12 @@ func getTestProvisioner(t *testing.T, caURL string) *Provisioner { } func TestNewProvisioner(t *testing.T) { - ca := startCATestServer() + ca := startCATestServer(t) defer ca.Close() want := getTestProvisioner(t, ca.URL) caBundle, err := os.ReadFile("testdata/secrets/root_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) type args struct { name string diff --git a/ca/tls.go b/ca/tls.go index d5d479f3..d7bed58a 100644 --- a/ca/tls.go +++ b/ca/tls.go @@ -69,7 +69,7 @@ func init() { GetClientCertificate: id.GetClientCertificateFunc(), }, } - return func(ctx context.Context, network, address string) (net.Conn, error) { + return func(ctx context.Context, _, _ string) (net.Conn, error) { return d.DialContext(ctx, "tcp", net.JoinHostPort(host, port)) } } diff --git a/ca/tls_options_test.go b/ca/tls_options_test.go index 7dea3dc8..4ac6ff85 100644 --- a/ca/tls_options_test.go +++ b/ca/tls_options_test.go @@ -10,6 +10,8 @@ import ( "sort" "testing" + "github.com/stretchr/testify/require" + "github.com/smallstep/certificates/api" ) @@ -130,7 +132,7 @@ func TestVerifyClientCertIfGiven(t *testing.T) { //nolint:gosec // test tls config func TestAddRootCA(t *testing.T) { - cert := parseCertificate(rootPEM) + cert := parseCertificate(t, rootPEM) pool := x509.NewCertPool() pool.AddCert(cert) @@ -163,7 +165,7 @@ func TestAddRootCA(t *testing.T) { //nolint:gosec // test tls config func TestAddClientCA(t *testing.T) { - cert := parseCertificate(rootPEM) + cert := parseCertificate(t, rootPEM) pool := x509.NewCertPool() pool.AddCert(cert) @@ -196,25 +198,19 @@ func TestAddClientCA(t *testing.T) { //nolint:gosec // test tls config func TestAddRootsToRootCAs(t *testing.T) { - ca := startCATestServer() + ca := startCATestServer(t) defer ca.Close() client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) root, err := os.ReadFile("testdata/secrets/root_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - cert := parseCertificate(string(root)) + cert := parseCertificate(t, string(root)) pool := x509.NewCertPool() pool.AddCert(cert) @@ -251,25 +247,19 @@ func TestAddRootsToRootCAs(t *testing.T) { //nolint:gosec // test tls config func TestAddRootsToClientCAs(t *testing.T) { - ca := startCATestServer() + ca := startCATestServer(t) defer ca.Close() client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) root, err := os.ReadFile("testdata/secrets/root_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - cert := parseCertificate(string(root)) + cert := parseCertificate(t, string(root)) pool := x509.NewCertPool() pool.AddCert(cert) @@ -306,31 +296,23 @@ func TestAddRootsToClientCAs(t *testing.T) { //nolint:gosec // test tls config func TestAddFederationToRootCAs(t *testing.T) { - ca := startCATestServer() + ca := startCATestServer(t) defer ca.Close() client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) root, err := os.ReadFile("testdata/secrets/root_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) federated, err := os.ReadFile("testdata/secrets/federated_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - crt1 := parseCertificate(string(root)) - crt2 := parseCertificate(string(federated)) + crt1 := parseCertificate(t, string(root)) + crt2 := parseCertificate(t, string(federated)) pool := x509.NewCertPool() pool.AddCert(crt1) pool.AddCert(crt2) @@ -371,31 +353,23 @@ func TestAddFederationToRootCAs(t *testing.T) { //nolint:gosec // test tls config func TestAddFederationToClientCAs(t *testing.T) { - ca := startCATestServer() + ca := startCATestServer(t) defer ca.Close() client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) root, err := os.ReadFile("testdata/secrets/root_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) federated, err := os.ReadFile("testdata/secrets/federated_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - crt1 := parseCertificate(string(root)) - crt2 := parseCertificate(string(federated)) + crt1 := parseCertificate(t, string(root)) + crt2 := parseCertificate(t, string(federated)) pool := x509.NewCertPool() pool.AddCert(crt1) pool.AddCert(crt2) @@ -436,25 +410,19 @@ func TestAddFederationToClientCAs(t *testing.T) { //nolint:gosec // test tls config func TestAddRootsToCAs(t *testing.T) { - ca := startCATestServer() + ca := startCATestServer(t) defer ca.Close() client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) root, err := os.ReadFile("testdata/secrets/root_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - cert := parseCertificate(string(root)) + cert := parseCertificate(t, string(root)) pool := x509.NewCertPool() pool.AddCert(cert) @@ -491,31 +459,23 @@ func TestAddRootsToCAs(t *testing.T) { //nolint:gosec // test tls config func TestAddFederationToCAs(t *testing.T) { - ca := startCATestServer() + ca := startCATestServer(t) defer ca.Close() client, err := NewClient(ca.URL, WithRootFile("testdata/secrets/root_ca.crt")) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) clientFail, err := NewClient(ca.URL, WithTransport(http.DefaultTransport)) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) root, err := os.ReadFile("testdata/secrets/root_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) federated, err := os.ReadFile("testdata/secrets/federated_ca.crt") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - crt1 := parseCertificate(string(root)) - crt2 := parseCertificate(string(federated)) + crt1 := parseCertificate(t, string(root)) + crt2 := parseCertificate(t, string(federated)) pool := x509.NewCertPool() pool.AddCert(crt1) pool.AddCert(crt2) diff --git a/ca/tls_test.go b/ca/tls_test.go index dbcc6023..d1ce11ea 100644 --- a/ca/tls_test.go +++ b/ca/tls_test.go @@ -17,27 +17,28 @@ import ( "testing" "time" - "github.com/smallstep/certificates/api" - "github.com/smallstep/certificates/authority" + "github.com/stretchr/testify/require" + "go.step.sm/crypto/jose" "go.step.sm/crypto/randutil" + + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority" ) -func generateOTT(subject string) string { +func generateOTT(t *testing.T, subject string) string { + t.Helper() now := time.Now() jwk, err := jose.ReadKey("testdata/secrets/ott_mariano_priv.jwk", jose.WithPassword([]byte("password"))) - if err != nil { - panic(err) - } + require.NoError(t, err) + opts := new(jose.SignerOptions).WithType("JWT").WithHeader("kid", jwk.KeyID) sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, opts) - if err != nil { - panic(err) - } + require.NoError(t, err) + id, err := randutil.ASCII(64) - if err != nil { - panic(err) - } + require.NoError(t, err) + cl := struct { jose.Claims SANS []string `json:"sans"` @@ -53,9 +54,8 @@ func generateOTT(subject string) string { SANS: []string{subject}, } raw, err := jose.Signed(sig).Claims(cl).CompactSerialize() - if err != nil { - panic(err) - } + require.NoError(t, err) + return raw } @@ -72,32 +72,28 @@ func startTestServer(baseContext context.Context, tlsConfig *tls.Config, handler return srv } -func startCATestServer() *httptest.Server { +func startCATestServer(t *testing.T) *httptest.Server { config, err := authority.LoadConfiguration("testdata/ca.json") - if err != nil { - panic(err) - } + require.NoError(t, err) ca, err := New(config) - if err != nil { - panic(err) - } + require.NoError(t, err) // Use a httptest.Server instead baseContext := buildContext(ca.auth, nil, nil, nil) srv := startTestServer(baseContext, ca.srv.TLSConfig, ca.srv.Handler) return srv } -func sign(domain string) (*Client, *api.SignResponse, crypto.PrivateKey) { - srv := startCATestServer() +func sign(t *testing.T, domain string) (*Client, *api.SignResponse, crypto.PrivateKey) { + t.Helper() + srv := startCATestServer(t) defer srv.Close() - return signDuration(srv, domain, 0) + return signDuration(t, srv, domain, 0) } -func signDuration(srv *httptest.Server, domain string, duration time.Duration) (*Client, *api.SignResponse, crypto.PrivateKey) { - req, pk, err := CreateSignRequest(generateOTT(domain)) - if err != nil { - panic(err) - } +func signDuration(t *testing.T, srv *httptest.Server, domain string, duration time.Duration) (*Client, *api.SignResponse, crypto.PrivateKey) { + t.Helper() + req, pk, err := CreateSignRequest(generateOTT(t, domain)) + require.NoError(t, err) if duration > 0 { req.NotBefore = api.NewTimeDuration(time.Now()) @@ -105,13 +101,11 @@ func signDuration(srv *httptest.Server, domain string, duration time.Duration) ( } client, err := NewClient(srv.URL, WithRootFile("testdata/secrets/root_ca.crt")) - if err != nil { - panic(err) - } + require.NoError(t, err) + sr, err := client.Sign(req) - if err != nil { - panic(err) - } + require.NoError(t, err) + return client, sr, pk } @@ -145,7 +139,7 @@ func serverHandler(t *testing.T, clientDomain string) http.Handler { func TestClient_GetServerTLSConfig_http(t *testing.T) { clientDomain := "test.domain" - client, sr, pk := sign("127.0.0.1") + client, sr, pk := sign(t, "127.0.0.1") // Create mTLS server ctx, cancel := context.WithCancel(context.Background()) @@ -212,7 +206,7 @@ func TestClient_GetServerTLSConfig_http(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - client, sr, pk := sign(clientDomain) + client, sr, pk := sign(t, clientDomain) cli := tt.getClient(t, client, sr, pk) if cli == nil { return @@ -246,19 +240,18 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) { defer reset() // Start CA - ca := startCATestServer() + ca := startCATestServer(t) defer ca.Close() clientDomain := "test.domain" - client, sr, pk := signDuration(ca, "127.0.0.1", 5*time.Second) + client, sr, pk := signDuration(t, ca, "127.0.0.1", 5*time.Second) // Start mTLS server ctx, cancel := context.WithCancel(context.Background()) defer cancel() tlsConfig, err := client.GetServerTLSConfig(ctx, sr, pk) - if err != nil { - t.Fatalf("Client.GetServerTLSConfig() error = %v", err) - } + require.NoError(t, err) + srvMTLS := startTestServer(context.Background(), tlsConfig, serverHandler(t, clientDomain)) defer srvMTLS.Close() @@ -266,30 +259,26 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) { ctx, cancel = context.WithCancel(context.Background()) defer cancel() tlsConfig, err = client.GetServerTLSConfig(ctx, sr, pk, VerifyClientCertIfGiven()) - if err != nil { - t.Fatalf("Client.GetServerTLSConfig() error = %v", err) - } + require.NoError(t, err) + srvTLS := startTestServer(context.Background(), tlsConfig, serverHandler(t, clientDomain)) defer srvTLS.Close() // Transport - client, sr, pk = signDuration(ca, clientDomain, 5*time.Second) + client, sr, pk = signDuration(t, ca, clientDomain, 5*time.Second) tr1, err := client.Transport(context.Background(), sr, pk) - if err != nil { - t.Fatalf("Client.Transport() error = %v", err) - } + require.NoError(t, err) + // Transport with tlsConfig - client, sr, pk = signDuration(ca, clientDomain, 5*time.Second) + client, sr, pk = signDuration(t, ca, clientDomain, 5*time.Second) tlsConfig, err = client.GetClientTLSConfig(context.Background(), sr, pk) - if err != nil { - t.Fatalf("Client.GetClientTLSConfig() error = %v", err) - } + require.NoError(t, err) + tr2 := getDefaultTransport(tlsConfig) // No client cert root, err := RootCertificate(sr) - if err != nil { - t.Fatalf("RootCertificate() error = %v", err) - } + require.NoError(t, err) + tlsConfig = getDefaultTLSConfig(sr) tlsConfig.RootCAs = x509.NewCertPool() tlsConfig.RootCAs.AddCert(root) @@ -401,13 +390,13 @@ func TestClient_GetServerTLSConfig_renew(t *testing.T) { } func TestCertificate(t *testing.T) { - cert := parseCertificate(certPEM) + cert := parseCertificate(t, certPEM) ok := &api.SignResponse{ ServerPEM: api.Certificate{Certificate: cert}, - CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)}, CertChainPEM: []api.Certificate{ {Certificate: cert}, - {Certificate: parseCertificate(rootPEM)}, + {Certificate: parseCertificate(t, rootPEM)}, }, } tests := []struct { @@ -434,12 +423,12 @@ func TestCertificate(t *testing.T) { } func TestIntermediateCertificate(t *testing.T) { - intermediate := parseCertificate(rootPEM) + intermediate := parseCertificate(t, rootPEM) ok := &api.SignResponse{ - ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, + ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)}, CaPEM: api.Certificate{Certificate: intermediate}, CertChainPEM: []api.Certificate{ - {Certificate: parseCertificate(certPEM)}, + {Certificate: parseCertificate(t, certPEM)}, {Certificate: intermediate}, }, } @@ -467,24 +456,24 @@ func TestIntermediateCertificate(t *testing.T) { } func TestRootCertificateCertificate(t *testing.T) { - root := parseCertificate(rootPEM) + root := parseCertificate(t, rootPEM) ok := &api.SignResponse{ - ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, - CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)}, + CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)}, CertChainPEM: []api.Certificate{ - {Certificate: parseCertificate(certPEM)}, - {Certificate: parseCertificate(rootPEM)}, + {Certificate: parseCertificate(t, certPEM)}, + {Certificate: parseCertificate(t, rootPEM)}, }, TLS: &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{ {root, root}, }}, } noTLS := &api.SignResponse{ - ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)}, - CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)}, + ServerPEM: api.Certificate{Certificate: parseCertificate(t, certPEM)}, + CaPEM: api.Certificate{Certificate: parseCertificate(t, rootPEM)}, CertChainPEM: []api.Certificate{ - {Certificate: parseCertificate(certPEM)}, - {Certificate: parseCertificate(rootPEM)}, + {Certificate: parseCertificate(t, certPEM)}, + {Certificate: parseCertificate(t, rootPEM)}, }, } tests := []struct { diff --git a/cas/apiv1/services.go b/cas/apiv1/services.go index bca24d96..00ecc2a8 100644 --- a/cas/apiv1/services.go +++ b/cas/apiv1/services.go @@ -53,6 +53,8 @@ const ( StepCAS = "stepcas" // VaultCAS is a CertificateAuthorityService using Hasicorp Vault PKI. VaultCAS = "vaultcas" + // ExternalCAS is a CertificateAuthorityService using an external injected CA implementation + ExternalCAS = "externalcas" ) // String returns a string from the type. It will always return the lower case @@ -65,6 +67,14 @@ func (t Type) String() string { return strings.ToLower(string(t)) } +// TypeOf returns the type of the given CertificateAuthorityService. +func TypeOf(c CertificateAuthorityService) Type { + if ct, ok := c.(interface{ Type() Type }); ok { + return ct.Type() + } + return ExternalCAS +} + // NotImplementedError is the type of error returned if an operation is not implemented. type NotImplementedError struct { Message string diff --git a/cas/apiv1/services_test.go b/cas/apiv1/services_test.go index 9289de76..2080f843 100644 --- a/cas/apiv1/services_test.go +++ b/cas/apiv1/services_test.go @@ -4,6 +4,24 @@ import ( "testing" ) +type simpleCAS struct{} + +func (*simpleCAS) CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error) { + return nil, NotImplementedError{} +} +func (*simpleCAS) RenewCertificate(req *RenewCertificateRequest) (*RenewCertificateResponse, error) { + return nil, NotImplementedError{} +} +func (*simpleCAS) RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error) { + return nil, NotImplementedError{} +} + +type fakeCAS struct { + simpleCAS +} + +func (*fakeCAS) Type() Type { return SoftCAS } + func TestType_String(t *testing.T) { tests := []struct { name string @@ -13,6 +31,7 @@ func TestType_String(t *testing.T) { {"default", "", "softcas"}, {"SoftCAS", SoftCAS, "softcas"}, {"CloudCAS", CloudCAS, "cloudcas"}, + {"ExternalCAS", ExternalCAS, "externalcas"}, {"UnknownCAS", "UnknownCAS", "unknowncas"}, } for _, tt := range tests { @@ -24,6 +43,27 @@ func TestType_String(t *testing.T) { } } +func TestTypeOf(t *testing.T) { + type args struct { + c CertificateAuthorityService + } + tests := []struct { + name string + args args + want Type + }{ + {"ok", args{&simpleCAS{}}, ExternalCAS}, + {"ok with type", args{&fakeCAS{}}, SoftCAS}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := TypeOf(tt.args.c); got != tt.want { + t.Errorf("TypeOf() = %v, want %v", got, tt.want) + } + }) + } +} + func TestNotImplementedError_Error(t *testing.T) { type fields struct { Message string diff --git a/cas/cloudcas/cloudcas.go b/cas/cloudcas/cloudcas.go index c9c8364f..398f7fed 100644 --- a/cas/cloudcas/cloudcas.go +++ b/cas/cloudcas/cloudcas.go @@ -154,6 +154,11 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) { }, nil } +// Type returns the type of this CertificateAuthorityService. +func (c *CloudCAS) Type() apiv1.Type { + return apiv1.CloudCAS +} + // GetCertificateAuthority returns the root certificate for the given // certificate authority. It implements apiv1.CertificateAuthorityGetter // interface. diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index 95446ee6..6e5d2133 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -443,6 +443,23 @@ func TestNew_real(t *testing.T) { } } +func TestCloudCAS_Type(t *testing.T) { + tests := []struct { + name string + want apiv1.Type + }{ + {"ok", apiv1.CloudCAS}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &CloudCAS{} + if got := c.Type(); got != tt.want { + t.Errorf("CloudCAS.Type() = %v, want %v", got, tt.want) + } + }) + } +} + func TestCloudCAS_GetCertificateAuthority(t *testing.T) { root := mustParseCertificate(t, testRootCertificate) type fields struct { diff --git a/cas/softcas/softcas.go b/cas/softcas/softcas.go index 58be8aab..dd961975 100644 --- a/cas/softcas/softcas.go +++ b/cas/softcas/softcas.go @@ -53,6 +53,11 @@ func New(_ context.Context, opts apiv1.Options) (*SoftCAS, error) { }, nil } +// Type returns the type of this CertificateAuthorityService. +func (c *SoftCAS) Type() apiv1.Type { + return apiv1.SoftCAS +} + // CreateCertificate signs a new certificate using Golang or KMS crypto. func (c *SoftCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { switch { diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 11bf217a..8c04de3a 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -252,6 +252,23 @@ func TestNew_register(t *testing.T) { } } +func TestSoftCAS_Type(t *testing.T) { + tests := []struct { + name string + want apiv1.Type + }{ + {"ok", apiv1.SoftCAS}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SoftCAS{} + if got := c.Type(); got != tt.want { + t.Errorf("SoftCAS.Type() = %v, want %v", got, tt.want) + } + }) + } +} + func TestSoftCAS_CreateCertificate(t *testing.T) { mockNow(t) // Set rand.Reader to EOF diff --git a/cas/stepcas/stepcas.go b/cas/stepcas/stepcas.go index 51c5f687..cab8f203 100644 --- a/cas/stepcas/stepcas.go +++ b/cas/stepcas/stepcas.go @@ -65,6 +65,11 @@ func New(ctx context.Context, opts apiv1.Options) (*StepCAS, error) { }, nil } +// Type returns the type of this CertificateAuthorityService. +func (s *StepCAS) Type() apiv1.Type { + return apiv1.StepCAS +} + // CreateCertificate uses the step-ca sign request with the configured // provisioner to get a new certificate from the certificate authority. func (s *StepCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { @@ -73,8 +78,8 @@ func (s *StepCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1 return nil, errors.New("createCertificateRequest `csr` cannot be nil") case req.Template == nil: return nil, errors.New("createCertificateRequest `template` cannot be nil") - case req.Lifetime == 0: - return nil, errors.New("createCertificateRequest `lifetime` cannot be 0") + case req.Lifetime < 0: + return nil, errors.New("createCertificateRequest `lifetime` cannot be less than 0") } info := &raInfo{ diff --git a/cas/stepcas/stepcas_test.go b/cas/stepcas/stepcas_test.go index f7746da0..d2846fb0 100644 --- a/cas/stepcas/stepcas_test.go +++ b/cas/stepcas/stepcas_test.go @@ -624,6 +624,23 @@ func TestNew(t *testing.T) { } } +func TestStepCAS_Type(t *testing.T) { + tests := []struct { + name string + want apiv1.Type + }{ + {"ok", apiv1.StepCAS}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &StepCAS{} + if got := c.Type(); got != tt.want { + t.Errorf("StepCAS.Type() = %v, want %v", got, tt.want) + } + }) + } +} + func TestStepCAS_CreateCertificate(t *testing.T) { caURL, client := testCAHelper(t) x5c := testX5CIssuer(t, caURL, "") diff --git a/cas/vaultcas/vaultcas.go b/cas/vaultcas/vaultcas.go index 5908cb7d..73d1b926 100644 --- a/cas/vaultcas/vaultcas.go +++ b/cas/vaultcas/vaultcas.go @@ -110,6 +110,11 @@ func New(ctx context.Context, opts apiv1.Options) (*VaultCAS, error) { }, nil } +// Type returns the type of this CertificateAuthorityService. +func (v *VaultCAS) Type() apiv1.Type { + return apiv1.VaultCAS +} + // CreateCertificate signs a new certificate using Hashicorp Vault. func (v *VaultCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { switch { diff --git a/cas/vaultcas/vaultcas_test.go b/cas/vaultcas/vaultcas_test.go index 0ea0c4b1..2f44bc05 100644 --- a/cas/vaultcas/vaultcas_test.go +++ b/cas/vaultcas/vaultcas_test.go @@ -193,6 +193,23 @@ func TestNew_register(t *testing.T) { } } +func TestVaultCAS_Type(t *testing.T) { + tests := []struct { + name string + want apiv1.Type + }{ + {"ok", apiv1.VaultCAS}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &VaultCAS{} + if got := c.Type(); got != tt.want { + t.Errorf("VaultCAS.Type() = %v, want %v", got, tt.want) + } + }) + } +} + func TestVaultCAS_CreateCertificate(t *testing.T) { _, client := testCAHelper(t) diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 289815ef..c7ece08f 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -33,6 +33,7 @@ import ( _ "go.step.sm/crypto/kms/pkcs11" _ "go.step.sm/crypto/kms/softkms" _ "go.step.sm/crypto/kms/sshagentkms" + _ "go.step.sm/crypto/kms/tpmkms" _ "go.step.sm/crypto/kms/yubikey" // Enabled cas interfaces. diff --git a/commands/app.go b/commands/app.go index e5c6ea1e..1ccaaf3c 100644 --- a/commands/app.go +++ b/commands/app.go @@ -239,7 +239,7 @@ To get a linked authority token: // replace resolver if requested if resolver != "" { net.DefaultResolver.PreferGo = true - net.DefaultResolver.Dial = func(ctx context.Context, network, address string) (net.Conn, error) { + net.DefaultResolver.Dial = func(_ context.Context, network, _ string) (net.Conn, error) { return net.Dial(network, resolver) } } @@ -251,7 +251,8 @@ To get a linked authority token: ca.WithSSHUserPassword(sshUserPassword), ca.WithIssuerPassword(issuerPassword), ca.WithLinkedCAToken(token), - ca.WithQuiet(quiet)) + ca.WithQuiet(quiet), + ) if err != nil { fatal(err) } diff --git a/db/db.go b/db/db.go index 39452672..503a7c14 100644 --- a/db/db.go +++ b/db/db.go @@ -116,7 +116,7 @@ func New(c *Config) (AuthDB, error) { opts := []nosql.Option{nosql.WithDatabase(c.Database), nosql.WithValueDir(c.ValueDir)} - if len(c.BadgerFileLoadingMode) > 0 { + if c.BadgerFileLoadingMode != "" { opts = append(opts, nosql.WithBadgerFileLoadingMode(c.BadgerFileLoadingMode)) } diff --git a/errs/error.go b/errs/error.go index ba066925..4ea5001e 100644 --- a/errs/error.go +++ b/errs/error.go @@ -49,10 +49,11 @@ func WithKeyVal(key string, val interface{}) Option { // Error represents the CA API errors. type Error struct { - Status int - Err error - Msg string - Details map[string]interface{} + Status int + Err error + Msg string + Details map[string]interface{} + RequestID string `json:"-"` } // ErrorResponse represents an error in JSON format. @@ -79,7 +80,7 @@ func (e *Error) StatusCode() int { // Message returns a user friendly error, if one is set. func (e *Error) Message() string { - if len(e.Msg) > 0 { + if e.Msg != "" { return e.Msg } return e.Err.Error() @@ -122,7 +123,7 @@ func Wrapf(status int, e error, format string, args ...interface{}) error { // MarshalJSON implements json.Marshaller interface for the Error struct. func (e *Error) MarshalJSON() ([]byte, error) { var msg string - if len(e.Msg) > 0 { + if e.Msg != "" { msg = e.Msg } else { msg = http.StatusText(e.Status) diff --git a/errs/errors_test.go b/errs/errors_test.go index 7b83c8d9..11590d7d 100644 --- a/errs/errors_test.go +++ b/errs/errors_test.go @@ -2,8 +2,9 @@ package errs import ( "fmt" - "reflect" "testing" + + "github.com/stretchr/testify/assert" ) func TestError_MarshalJSON(t *testing.T) { @@ -27,13 +28,14 @@ func TestError_MarshalJSON(t *testing.T) { Err: tt.fields.Err, } got, err := e.MarshalJSON() - if (err != nil) != tt.wantErr { - t.Errorf("Error.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + if tt.wantErr { + assert.Error(t, err) + assert.Empty(t, got) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Error.MarshalJSON() = %s, want %s", got, tt.want) - } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) }) } } @@ -54,13 +56,14 @@ func TestError_UnmarshalJSON(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := new(Error) - if err := e.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { - t.Errorf("Error.UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) - } - //nolint:govet // best option - if !reflect.DeepEqual(tt.expected, e) { - t.Errorf("Error.UnmarshalJSON() wants = %+v, got %+v", tt.expected, e) + err := e.UnmarshalJSON(tt.args.data) + if tt.wantErr { + assert.Error(t, err) + return } + + assert.NoError(t, err) + assert.Equal(t, tt.expected, e) }) } } diff --git a/examples/README.md b/examples/README.md index a2323302..a6908c6b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -21,7 +21,7 @@ the token does contain the root fingerprint then it is simpler to use: client, err := ca.Bootstrap(token) ``` -After the initialization there are examples of all the client methods. These +After the initialization, there are examples of all the client methods. These methods are a convenient way to use the CA API. The first method, `Health`, returns the status of the CA server. If the server is up it will return `{"status":"ok"}`. @@ -77,7 +77,7 @@ if err != nil { ... } ``` The following methods are for inpsecting Provisioners. -One method that returns a list of provisioners or a the encrypted key of one provisioner. +One method that returns a list of provisioners or an encrypted key of one provisioner. ```go // Without options it will return the first 20 provisioners. @@ -98,7 +98,7 @@ key, err := client.ProvisionerKey("DmAtZt2EhmZr_iTJJ387fr4Md2NbzMXGdXQNW1UWPXk") ``` The following example shows how to create a -tls.Config object that can be injected into servers and clients. By default these +tls.Config object that can be injected into servers and clients. By default, these methods will spin off Go routines that auto-renew a certificate once (approximately) two thirds of the duration of the certificate has passed. @@ -184,7 +184,7 @@ resp, err := client.Get("https://localhost:8443") ``` We will demonstrate the mTLS configuration in a different example. In this -examplefor we will configure the server to only verify client certificates +example we will configure the server to only verify client certificates if they are provided. To being with let's start the Step CA: @@ -226,7 +226,7 @@ If you'd like to turn off curl's verification of the certificate, use HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure. ``` -Now lets use the root certificate generated for the Step PKI. It should work. +Now let's use the root certificate generated for the Step PKI. It should work. ```sh certificates $ curl --cacert examples/pki/secrets/root_ca.crt https://localhost:8443 @@ -236,7 +236,7 @@ Hello nobody at 2018-11-03 01:49:25.66912 +0000 UTC!!! Notice that in the response we see `nobody`. This is because the server did not detected a TLS client configuration. -But if we create a client with it's own certificate (generated by the Step CA), +But if we create a client with its own certificate (generated by the Step CA), we should see the Common Name of the client certificate: ```sh @@ -304,7 +304,7 @@ We can use the bootstrap-server to demonstrate certificate rotation. We've added a second provisioner, named `mike@smallstep.com`, to the CA configuration. This provisioner is has a default certificate duration of 2 minutes. Let's run the server, and inspect the certificate. We can should be able to -see the certificate rotate once approximately 2/3rds of it's lifespan has passed. +see the certificate rotate once approximately 2/3rds of its lifespan has passed. ```sh certificates $ export STEPPATH=examples/pki @@ -320,7 +320,7 @@ The exact formula is `-/3-rand(/20)` (`duration=12 in our example). We can use the following command to check the certificate expiration and to make -sure the certificate changes after 74-80 seconds. +sure the certificate changes after 74-80 seconds. ```sh certificates $ step certificate inspect --insecure https://localhost:8443 diff --git a/examples/docker/renewer/entrypoint.sh b/examples/docker/renewer/entrypoint.sh index dc84dcbf..545f7fda 100755 --- a/examples/docker/renewer/entrypoint.sh +++ b/examples/docker/renewer/entrypoint.sh @@ -7,12 +7,12 @@ sleep 5 rm -f /var/local/step/root_ca.crt rm -f /var/local/step/site.crt /var/local/step/site.key -# Donwload the root certificate +# Download the root certificate step ca root /var/local/step/root_ca.crt # Get token STEP_TOKEN=$(step ca token $COMMON_NAME) -# Donwload the root certificate +# Download the root certificate step ca certificate --token $STEP_TOKEN $COMMON_NAME /var/local/step/site.crt /var/local/step/site.key -exec "$@" \ No newline at end of file +exec "$@" diff --git a/go.mod b/go.mod index 9f916ed2..c8162bd1 100644 --- a/go.mod +++ b/go.mod @@ -3,63 +3,78 @@ module github.com/smallstep/certificates go 1.20 require ( - cloud.google.com/go/longrunning v0.5.4 - cloud.google.com/go/security v1.15.4 + cloud.google.com/go/longrunning v0.5.6 + cloud.google.com/go/security v1.15.6 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.5.0 - github.com/go-chi/chi/v5 v5.0.11 - github.com/go-jose/go-jose/v3 v3.0.1 + github.com/fxamacker/cbor/v2 v2.6.0 + github.com/go-chi/chi/v5 v5.0.12 + 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.5.0 - github.com/googleapis/gax-go/v2 v2.12.0 - github.com/hashicorp/vault/api v1.10.0 - github.com/hashicorp/vault/api/auth/approle v0.5.0 - github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 - github.com/newrelic/go-agent/v3 v3.29.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/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.19.0 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.20230627102604-cf579e53cbd2 + github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 github.com/smallstep/nosql v0.6.0 github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/urfave/cli v1.22.14 - go.step.sm/cli-utils v0.8.0 - go.step.sm/crypto v0.40.0 + 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.17.0 + golang.org/x/crypto v0.21.0 golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 - golang.org/x/net v0.19.0 - google.golang.org/api v0.154.0 - google.golang.org/grpc v1.60.1 - google.golang.org/protobuf v1.32.0 + 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 ) require ( - cloud.google.com/go v0.110.10 // indirect - cloud.google.com/go/compute v1.23.3 // indirect + 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.5 // indirect - cloud.google.com/go/kms v1.15.5 // indirect + cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go/kms v1.15.7 // 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.9.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // 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/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.1.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // 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 v1.49.1 // 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/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/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 @@ -73,18 +88,18 @@ require ( 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.3.0 // indirect + github.com/go-logr/logr v1.4.1 // 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.0.0 // indirect - github.com/golang/glog v1.1.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.0 // indirect + github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // 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.2 // indirect + github.com/google/go-tpm-tools v0.4.3 // 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 @@ -100,19 +115,18 @@ require ( 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.0 // 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.2 // 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.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jackc/pgx/v4 v4.18.3 // 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 github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -120,8 +134,11 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/peterbourgon/diskv/v3 v3.0.1 // indirect - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/schollz/jsonstore v1.1.0 // indirect @@ -132,19 +149,19 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.etcd.io/bbolt v1.3.7 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect - golang.org/x/oauth2 v0.15.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.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/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d1859c29..b5466034 100644 --- a/go.sum +++ b/go.sum @@ -1,34 +1,34 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= -cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +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.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= -cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= -cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg= -cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= -cloud.google.com/go/security v1.15.4 h1:sdnh4Islb1ljaNhpIXlIPgb3eYj70QWgPVDKOUYvzJc= -cloud.google.com/go/security v1.15.4/go.mod h1:oN7C2uIZKhxCLiAAijKUCuHLZbIt/ghYEo8MqwD/Ty4= +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= 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.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +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/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.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= -github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +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/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= @@ -44,8 +44,36 @@ 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 v1.49.1 h1:Dsamcd8d/nNb3A+bZ0ucfGl0vGZsW5wlRW0vhoYGoeQ= -github.com/aws/aws-sdk-go v1.49.1/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +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/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/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= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= @@ -66,7 +94,6 @@ 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/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= 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= @@ -100,19 +127,18 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 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.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= -github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= -github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +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/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-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= @@ -124,8 +150,8 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE 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.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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/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= @@ -138,11 +164,11 @@ 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.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +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/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= @@ -161,8 +187,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD 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.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/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= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -180,14 +206,16 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ 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-sev-guest v0.9.3 h1:GOJ+EipURdeWFl/YYdgcCxyPeMgQUWlI056iFkBD8UU= -github.com/google/go-tdx-guest v0.2.3-0.20231011100059-4cf02bed9d33 h1:lRlUusuieEuqljjihCXb+Mr73VNitOYPJYWXzJKtBWs= +github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw= 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.2 h1:iyaCPKt2N5Rd0yz0G8ANa022SgCNZkMpp+db6QELtvI= -github.com/google/go-tpm-tools v0.4.2/go.mod h1:fGUDZu4tw3V4hUVuFHmiYgRd0c58/IXivn9v3Ea/ck4= +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-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= @@ -196,12 +224,12 @@ 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= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +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/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= @@ -227,12 +255,13 @@ 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.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= -github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= -github.com/hashicorp/vault/api/auth/approle v0.5.0 h1:a1TK6VGwYqSAfkmX4y4dJ4WBxMU5dStIZqScW4EPXR8= -github.com/hashicorp/vault/api/auth/approle v0.5.0/go.mod h1:CHOQIA1AZACfjTzHggmyfiOZ+xCSKNRFqe48FTCzH0k= -github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 h1:CXO0fD7M3iCGovP/UApeHhPcH4paDFKcu7AjEXi94rI= -github.com/hashicorp/vault/api/auth/kubernetes v0.5.0/go.mod h1:afrElBIO9Q4sHFVuVWgNevG4uAs1bT2AZFA9aEiI608= +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/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= @@ -249,8 +278,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +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= @@ -266,8 +295,8 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/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= @@ -281,16 +310,11 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08 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.0 h1:Ltaa1ePvc7msFGALnCrqKJVEByu/qYh5jJBYcDtAno4= -github.com/jackc/pgx/v4 v4.18.0/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +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/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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= @@ -326,8 +350,9 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd 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 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 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= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -347,23 +372,31 @@ 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.29.0 h1:Bc1D3DoOkpJs6aIzhOjUp+yIKJ2RfZ+LMQemZOs9t9k= -github.com/newrelic/go-agent/v3 v3.29.0/go.mod h1:9utrgxlSryNqRrTvII2XBL+0lpofXbqXApvVWPpbzUg= +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/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= 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= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/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_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= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +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/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -391,8 +424,8 @@ github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM= github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= -github.com/smallstep/go-attestation v0.4.4-0.20230627102604-cf579e53cbd2 h1:UIAS8DTWkeclraEGH2aiJPyNPu16VbT41w4JoBlyFfU= -github.com/smallstep/go-attestation v0.4.4-0.20230627102604-cf579e53cbd2/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +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/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= @@ -425,8 +458,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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= @@ -442,20 +476,21 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= 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.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.step.sm/cli-utils v0.8.0 h1:b/Tc1/m3YuQq+u3ghTFP7Dz5zUekZj6GUmd5pCvkEXQ= -go.step.sm/cli-utils v0.8.0/go.mod h1:S77aISrC0pKuflqiDfxxJlUbiXcAanyJ4POOnzFSxD4= -go.step.sm/crypto v0.40.0 h1:356UwJSM4Nhg5b5AjjjLlBNkf92Vw3Gi2r3vbEv72oc= -go.step.sm/crypto v0.40.0/go.mod h1:gfQMeTQXykihbS8e2Tdn0jtd9HbsQ7vbt+kp7efLA7U= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +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/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= @@ -465,6 +500,7 @@ 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.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= @@ -482,9 +518,11 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +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/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= @@ -496,6 +534,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG 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= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -509,19 +548,21 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= -golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= 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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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/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= @@ -543,22 +584,31 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +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/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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +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/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= @@ -568,6 +618,8 @@ 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= @@ -587,14 +639,15 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn 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.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050= -google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc= +google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= +google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= 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= @@ -602,19 +655,19 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ 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-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg= -google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= +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/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.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +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/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= @@ -626,16 +679,15 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD 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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/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= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/metrix/meter.go b/internal/metrix/meter.go new file mode 100644 index 00000000..334cf883 --- /dev/null +++ b/internal/metrix/meter.go @@ -0,0 +1,200 @@ +// Package metrix implements stats-related functionality. +package metrix + +import ( + "net/http" + "strconv" + "time" + + "github.com/smallstep/certificates/authority/provisioner" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// New initializes and returns a new [Meter]. +func New() (m *Meter) { + initializedAt := time.Now() + + m = &Meter{ + uptime: prometheus.NewGaugeFunc( + prometheus.GaugeOpts(opts( + "", + "uptime_seconds", + "Number of seconds since service start", + )), + func() float64 { + return float64(time.Since(initializedAt) / time.Second) + }, + ), + ssh: newProvisionerInstruments("ssh"), + x509: newProvisionerInstruments("x509"), + kms: &kms{ + signed: prometheus.NewCounter(prometheus.CounterOpts(opts("kms", "signed", "Number of KMS-backed signatures"))), + errors: prometheus.NewCounter(prometheus.CounterOpts(opts("kms", "errors", "Number of KMS-related errors"))), + }, + } + + reg := prometheus.NewRegistry() + + reg.MustRegister( + m.uptime, + m.ssh.rekeyed, + m.ssh.renewed, + m.ssh.signed, + m.ssh.webhookAuthorized, + m.ssh.webhookEnriched, + m.x509.rekeyed, + m.x509.renewed, + m.x509.signed, + m.x509.webhookAuthorized, + m.x509.webhookEnriched, + m.kms.signed, + m.kms.errors, + ) + + h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{ + Registry: reg, + Timeout: 5 * time.Second, + MaxRequestsInFlight: 10, + }) + + mux := http.NewServeMux() + mux.Handle("/metrics", h) + m.Handler = mux + + return +} + +// Meter wraps the functionality of a Prometheus-compatible HTTP handler. +type Meter struct { + http.Handler + + uptime prometheus.GaugeFunc + ssh *provisionerInstruments + x509 *provisionerInstruments + kms *kms +} + +// SSHRekeyed implements [authority.Meter] for [Meter]. +func (m *Meter) SSHRekeyed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.rekeyed, p, err) +} + +// SSHRenewed implements [authority.Meter] for [Meter]. +func (m *Meter) SSHRenewed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.renewed, p, err) +} + +// SSHSigned implements [authority.Meter] for [Meter]. +func (m *Meter) SSHSigned(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.signed, p, err) +} + +// SSHAuthorized 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]. +func (m *Meter) SSHWebhookEnriched(p provisioner.Interface, err error) { + incrProvisionerCounter(m.ssh.webhookEnriched, p, err) +} + +// X509Rekeyed implements [authority.Meter] for [Meter]. +func (m *Meter) X509Rekeyed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.rekeyed, p, err) +} + +// X509Renewed implements [authority.Meter] for [Meter]. +func (m *Meter) X509Renewed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.renewed, p, err) +} + +// X509Signed implements [authority.Meter] for [Meter]. +func (m *Meter) X509Signed(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.signed, p, err) +} + +// X509Authorized 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]. +func (m *Meter) X509WebhookEnriched(p provisioner.Interface, err error) { + incrProvisionerCounter(m.x509.webhookEnriched, p, err) +} + +func incrProvisionerCounter(cv *prometheus.CounterVec, p provisioner.Interface, err error) { + var name string + if p != nil { + name = p.GetName() + } + + cv.WithLabelValues(name, strconv.FormatBool(err == nil)).Inc() +} + +// KMSSigned implements [authority.Meter] for [Meter]. +func (m *Meter) KMSSigned(err error) { + if err == nil { + m.kms.signed.Inc() + } else { + m.kms.errors.Inc() + } +} + +// provisionerInstruments wraps the counters exported by provisioners. +type provisionerInstruments struct { + rekeyed *prometheus.CounterVec + renewed *prometheus.CounterVec + signed *prometheus.CounterVec + + webhookAuthorized *prometheus.CounterVec + webhookEnriched *prometheus.CounterVec +} + +func newProvisionerInstruments(subsystem string) *provisionerInstruments { + return &provisionerInstruments{ + rekeyed: newCounterVec(subsystem, "rekeyed_total", "Number of certificates rekeyed", + "provisioner", + "success", + ), + renewed: newCounterVec(subsystem, "renewed_total", "Number of certificates renewed", + "provisioner", + "success", + ), + signed: newCounterVec(subsystem, "signed_total", "Number of certificates signed", + "provisioner", + "success", + ), + webhookAuthorized: newCounterVec(subsystem, "webhook_authorized_total", "Number of authorizing webhooks called", + "provisioner", + "success", + ), + webhookEnriched: newCounterVec(subsystem, "webhook_enriched_total", "Number of enriching webhooks called", + "provisioner", + "success", + ), + } +} + +type kms struct { + signed prometheus.Counter + errors prometheus.Counter +} + +func newCounterVec(subsystem, name, help string, labels ...string) *prometheus.CounterVec { + opts := opts(subsystem, name, help) + + return prometheus.NewCounterVec(prometheus.CounterOpts(opts), labels) +} + +func opts(subsystem, name, help string) prometheus.Opts { + return prometheus.Opts{ + Namespace: "step_ca", + Subsystem: subsystem, + Name: name, + Help: help, + } +} diff --git a/internal/userid/userid.go b/internal/userid/userid.go new file mode 100644 index 00000000..48087da8 --- /dev/null +++ b/internal/userid/userid.go @@ -0,0 +1,20 @@ +package userid + +import "context" + +type contextKey struct{} + +// NewContext returns a new context with the given user ID added to the +// context. +// TODO(hs): this doesn't seem to be used / set currently; implement +// when/where it makes sense. +func NewContext(ctx context.Context, userID string) context.Context { + return context.WithValue(ctx, contextKey{}, userID) +} + +// FromContext returns the user ID from the context if it exists +// and is not empty. +func FromContext(ctx context.Context) (string, bool) { + v, ok := ctx.Value(contextKey{}).(string) + return v, ok && v != "" +} diff --git a/logging/context.go b/logging/context.go deleted file mode 100644 index b24b3638..00000000 --- a/logging/context.go +++ /dev/null @@ -1,66 +0,0 @@ -package logging - -import ( - "context" - "net/http" - - "github.com/rs/xid" -) - -type key int - -const ( - // RequestIDKey is the context key that should store the request identifier. - RequestIDKey key = iota - // UserIDKey is the context key that should store the user identifier. - UserIDKey -) - -// NewRequestID creates a new request id using github.com/rs/xid. -func NewRequestID() string { - return xid.New().String() -} - -// RequestID returns a new middleware that gets the given header and sets it -// in the context so it can be written in the logger. If the header does not -// exists or it's the empty string, it uses github.com/rs/xid to create a new -// one. -func RequestID(headerName string) func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, req *http.Request) { - requestID := req.Header.Get(headerName) - if requestID == "" { - requestID = NewRequestID() - req.Header.Set(headerName, requestID) - } - - ctx := WithRequestID(req.Context(), requestID) - next.ServeHTTP(w, req.WithContext(ctx)) - } - return http.HandlerFunc(fn) - } -} - -// WithRequestID returns a new context with the given requestID added to the -// context. -func WithRequestID(ctx context.Context, requestID string) context.Context { - return context.WithValue(ctx, RequestIDKey, requestID) -} - -// GetRequestID returns the request id from the context if it exists. -func GetRequestID(ctx context.Context) (string, bool) { - v, ok := ctx.Value(RequestIDKey).(string) - return v, ok -} - -// WithUserID decodes the token, extracts the user from the payload and stores -// it in the context. -func WithUserID(ctx context.Context, userID string) context.Context { - return context.WithValue(ctx, UserIDKey, userID) -} - -// GetUserID returns the request id from the context if it exists. -func GetUserID(ctx context.Context) (string, bool) { - v, ok := ctx.Value(UserIDKey).(string) - return v, ok -} diff --git a/logging/handler.go b/logging/handler.go index a8b77d60..701d631a 100644 --- a/logging/handler.go +++ b/logging/handler.go @@ -9,6 +9,9 @@ import ( "time" "github.com/sirupsen/logrus" + + "github.com/smallstep/certificates/internal/userid" + "github.com/smallstep/certificates/middleware/requestid" ) // LoggerHandler creates a logger handler @@ -29,16 +32,15 @@ type options struct { // NewLoggerHandler returns the given http.Handler with the logger integrated. func NewLoggerHandler(name string, logger *Logger, next http.Handler) http.Handler { - h := RequestID(logger.GetTraceHeader()) onlyTraceHealthEndpoint, _ := strconv.ParseBool(os.Getenv("STEP_LOGGER_ONLY_TRACE_HEALTH_ENDPOINT")) - return h(&LoggerHandler{ + return &LoggerHandler{ name: name, logger: logger.GetImpl(), options: options{ onlyTraceHealthEndpoint: onlyTraceHealthEndpoint, }, next: next, - }) + } } // ServeHTTP implements the http.Handler and call to the handler to log with a @@ -54,14 +56,14 @@ func (l *LoggerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // writeEntry writes to the Logger writer the request information in the logger. func (l *LoggerHandler) writeEntry(w ResponseLogger, r *http.Request, t time.Time, d time.Duration) { - var reqID, user string + var requestID, userID string ctx := r.Context() - if v, ok := ctx.Value(RequestIDKey).(string); ok && v != "" { - reqID = v + if v, ok := requestid.FromContext(ctx); ok { + requestID = v } - if v, ok := ctx.Value(UserIDKey).(string); ok && v != "" { - user = v + if v, ok := userid.FromContext(ctx); ok { + userID = v } // Remote hostname @@ -85,10 +87,10 @@ func (l *LoggerHandler) writeEntry(w ResponseLogger, r *http.Request, t time.Tim status := w.StatusCode() fields := logrus.Fields{ - "request-id": reqID, + "request-id": requestID, "remote-address": addr, "name": l.name, - "user-id": user, + "user-id": userID, "time": t.Format(time.RFC3339), "duration-ns": d.Nanoseconds(), "duration": d.String(), diff --git a/middleware/requestid/requestid.go b/middleware/requestid/requestid.go new file mode 100644 index 00000000..886ac147 --- /dev/null +++ b/middleware/requestid/requestid.go @@ -0,0 +1,92 @@ +// Package requestid provides HTTP request ID functionality +package requestid + +import ( + "context" + "net/http" + + "github.com/rs/xid" + + "go.step.sm/crypto/randutil" +) + +const ( + // requestIDHeader is the header name used for propagating request IDs. If + // available in an HTTP request, it'll be used instead of the X-Smallstep-Id + // header. It'll always be used in response and set to the request ID. + requestIDHeader = "X-Request-Id" + + // defaultTraceHeader is the default Smallstep tracing header that's currently + // in use. It is used as a fallback to retrieve a request ID from, if the + // "X-Request-Id" request header is not set. + defaultTraceHeader = "X-Smallstep-Id" +) + +type Handler struct { + legacyTraceHeader string +} + +// New creates a new request ID [handler]. It takes a trace header, +// which is used keep the legacy behavior intact, which relies on the +// X-Smallstep-Id header instead of X-Request-Id. +func New(legacyTraceHeader string) *Handler { + if legacyTraceHeader == "" { + legacyTraceHeader = defaultTraceHeader + } + + return &Handler{legacyTraceHeader: legacyTraceHeader} +} + +// Middleware wraps an [http.Handler] with request ID extraction +// from the X-Reqeust-Id header by default, or from the X-Smallstep-Id +// header if not set. If both are not set, a new request ID is generated. +// In all cases, the request ID is added to the request context, and +// set to be reflected in the response. +func (h *Handler) Middleware(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, req *http.Request) { + requestID := req.Header.Get(requestIDHeader) + if requestID == "" { + requestID = req.Header.Get(h.legacyTraceHeader) + } + + if requestID == "" { + requestID = newRequestID() + req.Header.Set(h.legacyTraceHeader, requestID) // legacy behavior + } + + // immediately set the request ID to be reflected in the response + w.Header().Set(requestIDHeader, requestID) + + // continue down the handler chain + ctx := NewContext(req.Context(), requestID) + next.ServeHTTP(w, req.WithContext(ctx)) + } + return http.HandlerFunc(fn) +} + +// newRequestID generates a new random UUIDv4 request ID. If UUIDv4 +// generation fails, it'll fallback to generating a random ID using +// github.com/rs/xid. +func newRequestID() string { + requestID, err := randutil.UUIDv4() + if err != nil { + requestID = xid.New().String() + } + + return requestID +} + +type contextKey struct{} + +// NewContext returns a new context with the given request ID added to the +// context. +func NewContext(ctx context.Context, requestID string) context.Context { + return context.WithValue(ctx, contextKey{}, requestID) +} + +// FromContext returns the request ID from the context if it exists and +// is not the empty value. +func FromContext(ctx context.Context) (string, bool) { + v, ok := ctx.Value(contextKey{}).(string) + return v, ok && v != "" +} diff --git a/middleware/requestid/requestid_test.go b/middleware/requestid/requestid_test.go new file mode 100644 index 00000000..84a9021f --- /dev/null +++ b/middleware/requestid/requestid_test.go @@ -0,0 +1,105 @@ +package requestid + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newRequest(t *testing.T) *http.Request { + t.Helper() + r, err := http.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + require.NoError(t, err) + return r +} + +func Test_Middleware(t *testing.T) { + requestWithID := newRequest(t) + requestWithID.Header.Set("X-Request-Id", "reqID") + + requestWithoutID := newRequest(t) + + requestWithEmptyHeader := newRequest(t) + requestWithEmptyHeader.Header.Set("X-Request-Id", "") + + requestWithSmallstepID := newRequest(t) + requestWithSmallstepID.Header.Set("X-Smallstep-Id", "smallstepID") + + tests := []struct { + name string + traceHeader string + next http.HandlerFunc + req *http.Request + }{ + { + name: "default-request-id", + traceHeader: defaultTraceHeader, + next: func(w http.ResponseWriter, r *http.Request) { + assert.Empty(t, r.Header.Get("X-Smallstep-Id")) + assert.Equal(t, "reqID", r.Header.Get("X-Request-Id")) + reqID, ok := FromContext(r.Context()) + if assert.True(t, ok) { + assert.Equal(t, "reqID", reqID) + } + assert.Equal(t, "reqID", w.Header().Get("X-Request-Id")) + }, + req: requestWithID, + }, + { + name: "no-request-id", + traceHeader: "X-Request-Id", + next: func(w http.ResponseWriter, r *http.Request) { + assert.Empty(t, r.Header.Get("X-Smallstep-Id")) + value := r.Header.Get("X-Request-Id") + assert.NotEmpty(t, value) + reqID, ok := FromContext(r.Context()) + if assert.True(t, ok) { + assert.Equal(t, value, reqID) + } + assert.Equal(t, value, w.Header().Get("X-Request-Id")) + }, + req: requestWithoutID, + }, + { + name: "empty-header", + traceHeader: "", + next: func(w http.ResponseWriter, r *http.Request) { + assert.Empty(t, r.Header.Get("X-Request-Id")) + value := r.Header.Get("X-Smallstep-Id") + assert.NotEmpty(t, value) + reqID, ok := FromContext(r.Context()) + if assert.True(t, ok) { + assert.Equal(t, value, reqID) + } + assert.Equal(t, value, w.Header().Get("X-Request-Id")) + }, + req: requestWithEmptyHeader, + }, + { + name: "fallback-header-name", + traceHeader: defaultTraceHeader, + next: func(w http.ResponseWriter, r *http.Request) { + assert.Empty(t, r.Header.Get("X-Request-Id")) + assert.Equal(t, "smallstepID", r.Header.Get("X-Smallstep-Id")) + reqID, ok := FromContext(r.Context()) + if assert.True(t, ok) { + assert.Equal(t, "smallstepID", reqID) + } + assert.Equal(t, "smallstepID", w.Header().Get("X-Request-Id")) + }, + req: requestWithSmallstepID, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + handler := New(tt.traceHeader).Middleware(tt.next) + + w := httptest.NewRecorder() + handler.ServeHTTP(w, tt.req) + assert.NotEmpty(t, w.Header().Get("X-Request-Id")) + }) + } +} diff --git a/monitoring/monitoring.go b/monitoring/monitoring.go index a0d0886b..1b679bdf 100644 --- a/monitoring/monitoring.go +++ b/monitoring/monitoring.go @@ -9,7 +9,9 @@ import ( "github.com/newrelic/go-agent/v3/newrelic" "github.com/pkg/errors" + "github.com/smallstep/certificates/logging" + "github.com/smallstep/certificates/middleware/requestid" ) // Middleware is a function returns another http.Handler that wraps the given @@ -82,7 +84,7 @@ func newRelicMiddleware(app *newrelic.Application) Middleware { txn.AddAttribute("httpResponseCode", strconv.Itoa(status)) // Add custom attributes - if v, ok := logging.GetRequestID(r.Context()); ok { + if v, ok := requestid.FromContext(r.Context()); ok { txn.AddAttribute("request.id", v) } diff --git a/policy/validate.go b/policy/validate.go index f7cf6e70..3ea42cc2 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -288,7 +288,7 @@ func checkNameConstraints( // domainToReverseLabels converts a textual domain name like foo.example.com to // the list of labels in reverse order, e.g. ["com", "example", "foo"]. func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { - for len(domain) > 0 { + for domain != "" { if i := strings.LastIndexByte(domain, '.'); i == -1 { reverseLabels = append(reverseLabels, domain) domain = "" @@ -401,7 +401,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) { } else { // Atom ("." Atom)* NextChar: - for len(in) > 0 { + for in != "" { // atext from RFC 2822, Section 3.2.4 c := in[0] diff --git a/scep/authority.go b/scep/authority.go index 1d156752..8ed065fb 100644 --- a/scep/authority.go +++ b/scep/authority.go @@ -60,7 +60,7 @@ func MustFromContext(ctx context.Context) *Authority { // SignAuthority is the interface for a signing authority type SignAuthority interface { - Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) + SignWithContext(ctx context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) LoadProvisionerByName(string) (provisioner.Interface, error) } @@ -306,7 +306,7 @@ func (a *Authority) SignCSR(ctx context.Context, csr *x509.CertificateRequest, m } signOps = append(signOps, templateOptions) - certChain, err := a.signAuth.Sign(csr, opts, signOps...) + certChain, err := a.signAuth.SignWithContext(ctx, csr, opts, signOps...) if err != nil { return nil, fmt.Errorf("error generating certificate for order: %w", err) } diff --git a/scripts/README.md b/scripts/README.md index 5571bf86..86c5bc86 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -4,5 +4,5 @@ Please note that `install-step-ra.sh` is referenced on the `files.smallstep.com` ## badger-migration -badger-migration is a tool that allows migrating data data from BadgerDB (v1 or +badger-migration is a tool that allows migrating data from BadgerDB (v1 or v2) to MySQL or PostgreSQL. diff --git a/scripts/install-step-ra.sh b/scripts/install-step-ra.sh index 07875601..0c08989e 100644 --- a/scripts/install-step-ra.sh +++ b/scripts/install-step-ra.sh @@ -184,14 +184,12 @@ if [ -z "$CA_PROVISIONER_JWK_PASSWORD_FILE" ]; then fi echo "Installing 'step-ca' in /usr/bin..." -CA_VERSION=$(curl -s https://api.github.com/repos/smallstep/certificates/releases/latest | jq -r '.tag_name') - -curl -sLO https://github.com/smallstep/certificates/releases/download/$CA_VERSION/step-ca_linux_${CA_VERSION:1}_$arch.tar.gz -tar -xf step-ca_linux_${CA_VERSION:1}_$arch.tar.gz -install -m 0755 -t /usr/bin step-ca_${CA_VERSION:1}/step-ca +curl -sLO https://dl.smallstep.com/certificates/ra-installer/latest/step-ca_linux_$arch.tar.gz +tar -xf step-ca_linux_$arch.tar.gz +install -m 0755 -t /usr/bin step-ca_linux_$arch/step-ca setcap CAP_NET_BIND_SERVICE=+eip $(which step-ca) -rm step-ca_linux_${CA_VERSION:1}_$arch.tar.gz -rm -rf step-ca_${CA_VERSION:1} +rm step-ca_linux_$arch.tar.gz +rm -rf step-ca_linux_$arch echo "Creating 'step' user..." export STEPPATH=/etc/step-ca diff --git a/test/integration/requestid_test.go b/test/integration/requestid_test.go new file mode 100644 index 00000000..54fd2eb0 --- /dev/null +++ b/test/integration/requestid_test.go @@ -0,0 +1,289 @@ +package integration + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "net" + "net/http" + "net/http/httptest" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.step.sm/crypto/jose" + "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/minica" + "go.step.sm/crypto/pemutil" + "go.step.sm/crypto/randutil" + "go.step.sm/crypto/x509util" + + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/ca" + "github.com/smallstep/certificates/ca/client" + "github.com/smallstep/certificates/errs" +) + +// 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 +} + +func Test_reflectRequestID(t *testing.T) { + dir := t.TempDir() + m, err := minica.New(minica.WithName("Step E2E")) + require.NoError(t, err) + + rootFilepath := filepath.Join(dir, "root.crt") + _, err = pemutil.Serialize(m.Root, pemutil.WithFilename(rootFilepath)) + require.NoError(t, err) + + intermediateCertFilepath := filepath.Join(dir, "intermediate.crt") + _, err = pemutil.Serialize(m.Intermediate, pemutil.WithFilename(intermediateCertFilepath)) + require.NoError(t, err) + + intermediateKeyFilepath := filepath.Join(dir, "intermediate.key") + _, err = pemutil.Serialize(m.Signer, pemutil.WithFilename(intermediateKeyFilepath)) + require.NoError(t, err) + + // get a random address to listen on and connect to; currently no nicer way to get one before starting the server + // TODO(hs): find/implement a nicer way to expose the CA URL, similar to how e.g. httptest.Server exposes it? + host, port := reservePort(t) + + authorizingSrv := newAuthorizingServer(t, m) + defer authorizingSrv.Close() + authorizingSrv.StartTLS() + + password := []byte("1234") + jwk, jwe, err := jose.GenerateDefaultKeyPair(password) + require.NoError(t, err) + encryptedKey, err := jwe.CompactSerialize() + require.NoError(t, err) + prov := &provisioner.JWK{ + ID: "jwk", + Name: "jwk", + Type: "JWK", + Key: jwk, + EncryptedKey: encryptedKey, + Claims: &config.GlobalProvisionerClaims, + Options: &provisioner.Options{ + Webhooks: []*provisioner.Webhook{ + { + ID: "webhook", + Name: "webhook-test", + URL: fmt.Sprintf("%s/authorize", authorizingSrv.URL), + Kind: "AUTHORIZING", + CertType: "X509", + }, + }, + }, + } + err = prov.Init(provisioner.Config{}) + require.NoError(t, err) + + cfg := &config.Config{ + Root: []string{rootFilepath}, + IntermediateCert: intermediateCertFilepath, + IntermediateKey: intermediateKeyFilepath, + Address: net.JoinHostPort(host, port), // reuse the address that was just "reserved" + DNSNames: []string{"127.0.0.1", "[::1]", "localhost"}, + AuthorityConfig: &config.AuthConfig{ + AuthorityID: "stepca-test", + DeploymentType: "standalone-test", + Provisioners: provisioner.List{prov}, + }, + Logger: json.RawMessage(`{"format": "text"}`), + } + c, err := ca.New(cfg) + require.NoError(t, err) + + // instantiate a client for the CA running at the random address + caClient, err := ca.NewClient( + fmt.Sprintf("https://localhost:%s", port), + ca.WithRootFile(rootFilepath), + ) + 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) + }() + + // require OK health response as the baseline + ctx := context.Background() + healthResponse, err := caClient.HealthWithContext(ctx) + require.NoError(t, err) + if assert.NotNil(t, healthResponse) { + require.Equal(t, "ok", healthResponse.Status) + } + + // expect an error when retrieving an invalid root + rootResponse, err := caClient.RootWithContext(ctx, "invalid") + var firstErr *errs.Error + if assert.ErrorAs(t, err, &firstErr) { + assert.Equal(t, 404, firstErr.StatusCode()) + assert.Equal(t, "The requested resource could not be found. Please see the certificate authority logs for more info.", firstErr.Err.Error()) + assert.NotEmpty(t, firstErr.RequestID) + + // TODO: include the below error in the JSON? It's currently only output to the CA logs. Also see https://github.com/smallstep/certificates/pull/759 + //assert.Equal(t, "/root/invalid was not found: certificate with fingerprint invalid was not found", apiErr.Msg) + } + assert.Nil(t, rootResponse) + + // expect an error when retrieving an invalid root and provided request ID + rootResponse, err = caClient.RootWithContext(client.NewRequestIDContext(ctx, "reqID"), "invalid") + var secondErr *errs.Error + if assert.ErrorAs(t, err, &secondErr) { + assert.Equal(t, 404, secondErr.StatusCode()) + assert.Equal(t, "The requested resource could not be found. Please see the certificate authority logs for more info.", secondErr.Err.Error()) + assert.Equal(t, "reqID", secondErr.RequestID) + } + assert.Nil(t, rootResponse) + + // prepare a Sign request + subject := "test" + decryptedJWK := decryptPrivateKey(t, jwe, password) + ott := generateOTT(t, decryptedJWK, subject) + + signer, err := keyutil.GenerateDefaultSigner() + require.NoError(t, err) + + csr, err := x509util.CreateCertificateRequest(subject, []string{subject}, signer) + require.NoError(t, err) + + // perform the Sign request using the OTT and CSR + signResponse, err := caClient.SignWithContext(client.NewRequestIDContext(ctx, "signRequestID"), &api.SignRequest{ + CsrPEM: api.CertificateRequest{CertificateRequest: csr}, + OTT: ott, + NotAfter: api.NewTimeDuration(time.Now().Add(1 * time.Hour)), + NotBefore: api.NewTimeDuration(time.Now().Add(-1 * time.Hour)), + }) + assert.NoError(t, err) + + // assert a certificate was returned for the subject "test" + if assert.NotNil(t, signResponse) { + assert.Len(t, signResponse.CertChainPEM, 2) + cert, err := x509.ParseCertificate(signResponse.CertChainPEM[0].Raw) + assert.NoError(t, err) + if assert.NotNil(t, cert) { + assert.Equal(t, "test", cert.Subject.CommonName) + assert.Contains(t, cert.DNSNames, "test") + } + } + + // done testing; stop and wait for the server to quit + err = c.Stop() + require.NoError(t, err) + + wg.Wait() +} + +func decryptPrivateKey(t *testing.T, jwe *jose.JSONWebEncryption, pass []byte) *jose.JSONWebKey { + t.Helper() + d, err := jwe.Decrypt(pass) + require.NoError(t, err) + + jwk := &jose.JSONWebKey{} + err = json.Unmarshal(d, jwk) + require.NoError(t, err) + + return jwk +} + +func generateOTT(t *testing.T, jwk *jose.JSONWebKey, subject string) string { + t.Helper() + now := time.Now() + + keyID, err := jose.Thumbprint(jwk) + require.NoError(t, err) + + opts := new(jose.SignerOptions).WithType("JWT").WithHeader("kid", keyID) + signer, err := jose.NewSigner(jose.SigningKey{Key: jwk.Key}, opts) + require.NoError(t, err) + + id, err := randutil.ASCII(64) + require.NoError(t, err) + + cl := struct { + jose.Claims + SANS []string `json:"sans"` + }{ + Claims: jose.Claims{ + ID: id, + Subject: subject, + Issuer: "jwk", + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(time.Minute)), + Audience: []string{"https://127.0.0.1/1.0/sign"}, + }, + SANS: []string{subject}, + } + raw, err := jose.Signed(signer).Claims(cl).CompactSerialize() + require.NoError(t, err) + + return raw +} + +func newAuthorizingServer(t *testing.T, mca *minica.CA) *httptest.Server { + t.Helper() + + key, err := keyutil.GenerateDefaultSigner() + require.NoError(t, err) + + csr, err := x509util.CreateCertificateRequest("127.0.0.1", []string{"127.0.0.1"}, key) + require.NoError(t, err) + + crt, err := mca.SignCSR(csr) + require.NoError(t, err) + + srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if assert.Equal(t, "signRequestID", r.Header.Get("X-Request-Id")) { + json.NewEncoder(w).Encode(struct{ Allow bool }{Allow: true}) + w.WriteHeader(http.StatusOK) + return + } + + w.WriteHeader(http.StatusBadRequest) + })) + trustedRoots := x509.NewCertPool() + trustedRoots.AddCert(mca.Root) + + srv.TLS = &tls.Config{ + Certificates: []tls.Certificate{ + { + Certificate: [][]byte{crt.Raw, mca.Intermediate.Raw}, + PrivateKey: key, + Leaf: crt, + }, + }, + ClientCAs: trustedRoots, + ClientAuth: tls.RequireAndVerifyClientCert, + ServerName: "localhost", + } + + return srv +}