Merge branch 'master' into herman/acme-da-tpm

This commit is contained in:
Herman Slatman 2023-01-23 22:52:56 +01:00
commit f1724ea8c5
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
46 changed files with 922 additions and 3445 deletions

View file

@ -1,41 +1,108 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
### TEMPLATE -- do not alter or remove ## TEMPLATE -- do not alter or remove
--- ---
## [x.y.z] - aaaa-bb-cc ## [x.y.z] - aaaa-bb-cc
### Added ### Added
### Changed ### Changed
### Deprecated ### Deprecated
### Removed ### Removed
### Fixed ### Fixed
### Security ### Security
--- ---
## [Unreleased] ## [Unreleased]
## [v0.23.1] - 2022-01-10
### Added ### Added
- Added support for ACME device-attest-01 challenge.
- Added configuration property `.crl.idpURL` to be able to set a custom Issuing
Distribution Point in the CRL (smallstep/certificates#1178).
- Added WithContext methods to the CA client (smallstep/certificates#1211).
- Docker: Added environment variables for enabling Remote Management and ACME
provisioner (smallstep/certificates#1201).
- Docker: The entrypoint script now generates and displays an initial JWK
provisioner password by default when the CA is being initialized
(smallstep/certificates#1223).
### Changed
- Ignore SSH principals validation when using an OIDC provisioner. The
provisioner will ignore the principals passed and set the defaults or the ones
including using WebHooks or templates (smallstep/certificates#1206).
## [v0.23.0] - 2022-11-11
### Added
- Added support for ACME device-attest-01 challenge on iOS, iPadOS, tvOS and
YubiKey.
- Ability to disable ACME challenges and attestation formats.
- Added flags to change ACME challenge ports for testing purposes.
- Added name constraints evaluation and enforcement when issuing or renewing - Added name constraints evaluation and enforcement when issuing or renewing
X.509 certificates. X.509 certificates.
- Added provisioner webhooks for augmenting template data and authorizing certificate requests before signing. - Added provisioner webhooks for augmenting template data and authorizing
- Added automatic migration of provisioners when enabling remote managment. certificate requests before signing.
- Added automatic migration of provisioners when enabling remote management.
- Added experimental support for CRLs. - Added experimental support for CRLs.
- Add certificate renewal support on RA mode. The `step ca renew` command must
use the flag `--mtls=false` to use the token renewal flow.
- Added support for initializing remote management using `step ca init`.
- Added support for renewing X.509 certificates on RAs.
- Added support for using SCEP with keys in a KMS.
- Added client support to set the dialer's local address with the environment variable
`STEP_CLIENT_ADDR`.
### Changed
- Remove the email requirement for issuing SSH certificates with an OIDC
provisioner.
- Root files can contain more than one certificate.
### Fixed ### Fixed
- MySQL DSN parsing issues fixed with upgrade to [smallstep/nosql@v0.5.0](https://github.com/smallstep/nosql/releases/tag/v0.5.0).
- Fixed MySQL DSN parsing issues with an upgrade to
[smallstep/nosql@v0.5.0](https://github.com/smallstep/nosql/releases/tag/v0.5.0).
- Fixed renewal of certificates with missing subject attributes.
- Fixed ACME support with [ejabberd](https://github.com/processone/ejabberd).
### Deprecated
- The CLIs `step-awskms-init`, `step-cloudkms-init`, `step-pkcs11-init`,
`step-yubikey-init` are deprecated. Now you can use
[`step-kms-plugin`](https://github.com/smallstep/step-kms-plugin) in
combination with `step certificates create` to initialize your PKI.
## [0.22.1] - 2022-08-31 ## [0.22.1] - 2022-08-31
### Fixed ### Fixed
- Fixed signature algorithm on EC (root) + RSA (intermediate) PKIs. - Fixed signature algorithm on EC (root) + RSA (intermediate) PKIs.
## [0.22.0] - 2022-08-26 ## [0.22.0] - 2022-08-26
### Added ### Added
- Added automatic configuration of Linked RAs. - Added automatic configuration of Linked RAs.
- Send provisioner configuration on Linked RAs. - Send provisioner configuration on Linked RAs.
### Changed ### Changed
- Certificates signed by an issuer using an RSA key will be signed using the - Certificates signed by an issuer using an RSA key will be signed using the
same algorithm used to sign the issuer certificate. The signature will no same algorithm used to sign the issuer certificate. The signature will no
longer default to PKCS #1. For example, if the issuer certificate was signed longer default to PKCS #1. For example, if the issuer certificate was signed
@ -47,20 +114,28 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Sanitize TLS options. - Sanitize TLS options.
## [0.20.0] - 2022-05-26 ## [0.20.0] - 2022-05-26
### Added ### Added
- Added Kubernetes auth method for Vault RAs. - Added Kubernetes auth method for Vault RAs.
- Added support for reporting provisioners to linkedca. - Added support for reporting provisioners to linkedca.
- Added support for certificate policies on authority level. - Added support for certificate policies on authority level.
- Added a Dockerfile with a step-ca build with HSM support. - Added a Dockerfile with a step-ca build with HSM support.
- A few new WithXX methods for instantiating authorities - A few new WithXX methods for instantiating authorities
### Changed ### Changed
- Context usage in HTTP APIs. - Context usage in HTTP APIs.
- Changed authentication for Vault RAs. - Changed authentication for Vault RAs.
- Error message returned to client when authenticating with expired certificate. - Error message returned to client when authenticating with expired certificate.
- Strip padding from ACME CSRs. - Strip padding from ACME CSRs.
### Deprecated ### Deprecated
- HTTP API handler types. - HTTP API handler types.
### Fixed ### Fixed
- Fixed SSH revocation. - Fixed SSH revocation.
- CA client dial context for js/wasm target. - CA client dial context for js/wasm target.
- Incomplete `extraNames` support in templates. - Incomplete `extraNames` support in templates.
@ -68,7 +143,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Large SCEP request handling. - Large SCEP request handling.
## [0.19.0] - 2022-04-19 ## [0.19.0] - 2022-04-19
### Added ### Added
- Added support for certificate renewals after expiry using the claim `allowRenewalAfterExpiry`. - Added support for certificate renewals after expiry using the claim `allowRenewalAfterExpiry`.
- Added support for `extraNames` in X.509 templates. - Added support for `extraNames` in X.509 templates.
- Added `armv5` builds. - Added `armv5` builds.
@ -83,104 +160,156 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
on startup, the configuration for the current context is used. on startup, the configuration for the current context is used.
- Added startup info logging and option to skip it (`--quiet`). - Added startup info logging and option to skip it (`--quiet`).
- Added support for renaming the CA (Common Name). - Added support for renaming the CA (Common Name).
### Changed ### Changed
- Made SCEP CA URL paths dynamic. - Made SCEP CA URL paths dynamic.
- Support two latest versions of Go (1.17, 1.18). - Support two latest versions of Go (1.17, 1.18).
- Upgrade go.step.sm/crypto to v0.16.1. - Upgrade go.step.sm/crypto to v0.16.1.
- Upgrade go.step.sm/linkedca to v0.15.0. - Upgrade go.step.sm/linkedca to v0.15.0.
### Deprecated ### Deprecated
- Go 1.16 support. - Go 1.16 support.
### Removed ### Removed
### Fixed ### Fixed
- Fixed admin credentials on RAs. - Fixed admin credentials on RAs.
- Fixed ACME HTTP-01 challenges for IPv6 identifiers. - Fixed ACME HTTP-01 challenges for IPv6 identifiers.
- Various improvements under the hood. - Various improvements under the hood.
### Security ### Security
## [0.18.2] - 2022-03-01 ## [0.18.2] - 2022-03-01
### Added ### Added
- Added `subscriptionIDs` and `objectIDs` filters to the Azure provisioner. - Added `subscriptionIDs` and `objectIDs` filters to the Azure provisioner.
- [NoSQL](https://github.com/smallstep/nosql/pull/21) package allows filtering - [NoSQL](https://github.com/smallstep/nosql/pull/21) package allows filtering
out database drivers using Go tags. For example, using the Go flag out database drivers using Go tags. For example, using the Go flag
`--tags=nobadger,nobbolt,nomysql` will only compile `step-ca` with the pgx `--tags=nobadger,nobbolt,nomysql` will only compile `step-ca` with the pgx
driver for PostgreSQL. driver for PostgreSQL.
### Changed ### Changed
- IPv6 addresses are normalized as IP addresses instead of hostnames. - IPv6 addresses are normalized as IP addresses instead of hostnames.
- More descriptive JWK decryption error message. - More descriptive JWK decryption error message.
- Make the X5C leaf certificate available to the templates using `{{ .AuthorizationCrt }}`. - Make the X5C leaf certificate available to the templates using `{{ .AuthorizationCrt }}`.
### Fixed ### Fixed
- During provisioner add - validate provisioner configuration before storing to DB. - During provisioner add - validate provisioner configuration before storing to DB.
## [0.18.1] - 2022-02-03 ## [0.18.1] - 2022-02-03
### Added ### Added
- Support for ACME revocation. - Support for ACME revocation.
- Replace hash function with an RSA SSH CA to "rsa-sha2-256". - Replace hash function with an RSA SSH CA to "rsa-sha2-256".
- Support Nebula provisioners. - Support Nebula provisioners.
- Example Ansible configurations. - Example Ansible configurations.
- Support PKCS#11 as a decrypter, as used by SCEP. - Support PKCS#11 as a decrypter, as used by SCEP.
### Changed ### Changed
- Automatically create database directory on `step ca init`. - Automatically create database directory on `step ca init`.
- Slightly improve errors reported when a template has invalid content. - Slightly improve errors reported when a template has invalid content.
- Error reporting in logs and to clients. - Error reporting in logs and to clients.
### Fixed ### Fixed
- SCEP renewal using HTTPS on macOS. - SCEP renewal using HTTPS on macOS.
## [0.18.0] - 2021-11-17 ## [0.18.0] - 2021-11-17
### Added ### Added
- Support for multiple certificate authority contexts. - Support for multiple certificate authority contexts.
- Support for generating extractable keys and certificates on a pkcs#11 module. - Support for generating extractable keys and certificates on a pkcs#11 module.
### Changed ### Changed
- Support two latest versions of Go (1.16, 1.17) - Support two latest versions of Go (1.16, 1.17)
### Deprecated ### Deprecated
- go 1.15 support - go 1.15 support
## [0.17.6] - 2021-10-20 ## [0.17.6] - 2021-10-20
### Notes ### Notes
- 0.17.5 failed in CI/CD - 0.17.5 failed in CI/CD
## [0.17.5] - 2021-10-20 ## [0.17.5] - 2021-10-20
### Added ### Added
- Support for Azure Key Vault as a KMS. - Support for Azure Key Vault as a KMS.
- Adapt `pki` package to support key managers. - Adapt `pki` package to support key managers.
- gocritic linter - gocritic linter
### Fixed ### Fixed
- gocritic warnings - gocritic warnings
## [0.17.4] - 2021-09-28 ## [0.17.4] - 2021-09-28
### Fixed ### Fixed
- Support host-only or user-only SSH CA. - Support host-only or user-only SSH CA.
## [0.17.3] - 2021-09-24 ## [0.17.3] - 2021-09-24
### Added ### Added
- go 1.17 to github action test matrix - go 1.17 to github action test matrix
- Support for CloudKMS RSA-PSS signers without using templates. - Support for CloudKMS RSA-PSS signers without using templates.
- Add flags to support individual passwords for the intermediate and SSH keys. - Add flags to support individual passwords for the intermediate and SSH keys.
- Global support for group admins in the OIDC provisioner. - Global support for group admins in the OIDC provisioner.
### Changed ### Changed
- Using go 1.17 for binaries - Using go 1.17 for binaries
### Fixed ### Fixed
- Upgrade go-jose.v2 to fix a bug in the JWK fingerprint of Ed25519 keys. - Upgrade go-jose.v2 to fix a bug in the JWK fingerprint of Ed25519 keys.
### Security ### Security
- Use cosign to sign and upload signatures for multi-arch Docker container. - Use cosign to sign and upload signatures for multi-arch Docker container.
- Add debian checksum - Add debian checksum
## [0.17.2] - 2021-08-30 ## [0.17.2] - 2021-08-30
### Added ### Added
- Additional way to distinguish Azure IID and Azure OIDC tokens. - Additional way to distinguish Azure IID and Azure OIDC tokens.
### Security ### Security
- Sign over all goreleaser github artifacts using cosign - Sign over all goreleaser github artifacts using cosign
## [0.17.1] - 2021-08-26 ## [0.17.1] - 2021-08-26
## [0.17.0] - 2021-08-25 ## [0.17.0] - 2021-08-25
### Added ### Added
- Add support for Linked CAs using protocol buffers and gRPC - Add support for Linked CAs using protocol buffers and gRPC
- `step-ca init` adds support for - `step-ca init` adds support for
- configuring a StepCAS RA - configuring a StepCAS RA
- configuring a Linked CA - configuring a Linked CA
- congifuring a `step-ca` using Helm - congifuring a `step-ca` using Helm
### Changed ### Changed
- Update badger driver to use v2 by default - Update badger driver to use v2 by default
- Update TLS cipher suites to include 1.3 - Update TLS cipher suites to include 1.3
### Security ### Security
- Fix key version when SHA512WithRSA is used. There was a typo creating RSA keys with SHA256 digests instead of SHA512. - Fix key version when SHA512WithRSA is used. There was a typo creating RSA keys with SHA256 digests instead of SHA512.

View file

@ -394,6 +394,6 @@ func GetCertificate(w http.ResponseWriter, r *http.Request) {
} }
api.LogCertificate(w, cert.Leaf) api.LogCertificate(w, cert.Leaf)
w.Header().Set("Content-Type", "application/pem-certificate-chain; charset=utf-8") w.Header().Set("Content-Type", "application/pem-certificate-chain")
w.Write(certBytes) w.Write(certBytes)
} }

View file

@ -514,7 +514,7 @@ func TestHandler_GetCertificate(t *testing.T) {
assert.Equals(t, res.Header["Content-Type"], []string{"application/problem+json"}) assert.Equals(t, res.Header["Content-Type"], []string{"application/problem+json"})
} else { } else {
assert.Equals(t, bytes.TrimSpace(body), bytes.TrimSpace(certBytes)) assert.Equals(t, bytes.TrimSpace(body), bytes.TrimSpace(certBytes))
assert.Equals(t, res.Header["Content-Type"], []string{"application/pem-certificate-chain; charset=utf-8"}) assert.Equals(t, res.Header["Content-Type"], []string{"application/pem-certificate-chain"})
} }
}) })
} }

View file

@ -7,8 +7,6 @@ import (
"os" "os"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/logging"
) )
// StackTracedError is the set of errors implementing the StackTrace function. // StackTracedError is the set of errors implementing the StackTrace function.
@ -21,16 +19,21 @@ type StackTracedError interface {
StackTrace() errors.StackTrace StackTrace() errors.StackTrace
} }
type fieldCarrier interface {
WithFields(map[string]any)
Fields() map[string]any
}
// Error adds to the response writer the given error if it implements // Error adds to the response writer the given error if it implements
// logging.ResponseLogger. If it does not implement it, then writes the error // logging.ResponseLogger. If it does not implement it, then writes the error
// using the log package. // using the log package.
func Error(rw http.ResponseWriter, err error) { func Error(rw http.ResponseWriter, err error) {
rl, ok := rw.(logging.ResponseLogger) fc, ok := rw.(fieldCarrier)
if !ok { if !ok {
return return
} }
rl.WithFields(map[string]interface{}{ fc.WithFields(map[string]any{
"error": err, "error": err,
}) })
@ -39,8 +42,8 @@ func Error(rw http.ResponseWriter, err error) {
} }
var st StackTracedError var st StackTracedError
if !errors.As(err, &st) { if errors.As(err, &st) {
rl.WithFields(map[string]interface{}{ fc.WithFields(map[string]any{
"stack-trace": fmt.Sprintf("%+v", st.StackTrace()), "stack-trace": fmt.Sprintf("%+v", st.StackTrace()),
}) })
} }
@ -48,9 +51,9 @@ func Error(rw http.ResponseWriter, err error) {
// EnabledResponse log the response object if it implements the EnableLogger // EnabledResponse log the response object if it implements the EnableLogger
// interface. // interface.
func EnabledResponse(rw http.ResponseWriter, v interface{}) { func EnabledResponse(rw http.ResponseWriter, v any) {
type enableLogger interface { type enableLogger interface {
ToLog() (interface{}, error) ToLog() (any, error)
} }
if el, ok := v.(enableLogger); ok { if el, ok := v.(enableLogger); ok {
@ -61,8 +64,8 @@ func EnabledResponse(rw http.ResponseWriter, v interface{}) {
return return
} }
if rl, ok := rw.(logging.ResponseLogger); ok { if rl, ok := rw.(fieldCarrier); ok {
rl.WithFields(map[string]interface{}{ rl.WithFields(map[string]any{
"response": out, "response": out,
}) })
} }

View file

@ -1,43 +1,78 @@
package log package log
import ( import (
"errors"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect"
"testing" "testing"
"unsafe"
pkgerrors "github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
) )
func TestError(t *testing.T) { type stackTracedError struct{}
theError := errors.New("the error")
type args struct { func (stackTracedError) Error() string {
rw http.ResponseWriter return "a stacktraced error"
err error }
func (stackTracedError) StackTrace() pkgerrors.StackTrace {
f := struct{}{}
return pkgerrors.StackTrace{ // fake stacktrace
pkgerrors.Frame(unsafe.Pointer(&f)),
pkgerrors.Frame(unsafe.Pointer(&f)),
} }
}
func TestError(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args error
withFields bool rw http.ResponseWriter
isFieldCarrier bool
stepDebug bool
expectStackTrace bool
}{ }{
{"normalLogger", args{httptest.NewRecorder(), theError}, false}, {"noLogger", nil, nil, false, false, false},
{"responseLogger", args{logging.NewResponseLogger(httptest.NewRecorder()), theError}, true}, {"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false},
{"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false},
{"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false},
{"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false},
{"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true},
{"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
Error(tt.args.rw, tt.args.err) if tt.stepDebug {
if tt.withFields { t.Setenv("STEPDEBUG", "1")
if rl, ok := tt.args.rw.(logging.ResponseLogger); ok {
fields := rl.Fields()
if !reflect.DeepEqual(fields["error"], theError) {
t.Errorf("ResponseLogger[\"error\"] = %s, wants %s", fields["error"], theError)
}
} else { } else {
t.Error("ResponseWriter does not implement logging.ResponseLogger") t.Setenv("STEPDEBUG", "0")
} }
Error(tt.rw, tt.error)
// return early if test case doesn't use logger
if !tt.isFieldCarrier {
return
}
fields := tt.rw.(logging.ResponseLogger).Fields()
// expect the error field to be (not) set and to be the same error that was fed to Error
if tt.error == nil {
assert.Nil(t, fields["error"])
} else {
assert.Same(t, tt.error, fields["error"])
}
// check if stack-trace is set when expected
if _, hasStackTrace := fields["stack-trace"]; tt.expectStackTrace && !hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] not set`)
} else if !tt.expectStackTrace && hasStackTrace {
t.Error(`ResponseLogger["stack-trace"] was set`)
} }
}) })
} }

View file

@ -413,13 +413,13 @@ func (a *Authority) init() error {
// Read root certificates and store them in the certificates map. // Read root certificates and store them in the certificates map.
if len(a.rootX509Certs) == 0 { if len(a.rootX509Certs) == 0 {
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root)) a.rootX509Certs = make([]*x509.Certificate, 0, len(a.config.Root))
for i, path := range a.config.Root { for _, path := range a.config.Root {
crt, err := pemutil.ReadCertificate(path) crts, err := pemutil.ReadCertificateBundle(path)
if err != nil { if err != nil {
return err return err
} }
a.rootX509Certs[i] = crt a.rootX509Certs = append(a.rootX509Certs, crts...)
} }
} }
for _, crt := range a.rootX509Certs { for _, crt := range a.rootX509Certs {
@ -434,13 +434,13 @@ func (a *Authority) init() error {
// Read federated certificates and store them in the certificates map. // Read federated certificates and store them in the certificates map.
if len(a.federatedX509Certs) == 0 { if len(a.federatedX509Certs) == 0 {
a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots)) a.federatedX509Certs = make([]*x509.Certificate, 0, len(a.config.FederatedRoots))
for i, path := range a.config.FederatedRoots { for _, path := range a.config.FederatedRoots {
crt, err := pemutil.ReadCertificate(path) crts, err := pemutil.ReadCertificateBundle(path)
if err != nil { if err != nil {
return err return err
} }
a.federatedX509Certs[i] = crt a.federatedX509Certs = append(a.federatedX509Certs, crts...)
} }
} }
for _, crt := range a.federatedX509Certs { for _, crt := range a.federatedX509Certs {

View file

@ -6,8 +6,10 @@ import (
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"encoding/hex" "encoding/hex"
"encoding/pem"
"net" "net"
"os" "os"
"path/filepath"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -18,6 +20,7 @@ import (
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
) )
@ -172,6 +175,130 @@ func TestAuthorityNew(t *testing.T) {
} }
} }
func TestAuthorityNew_bundles(t *testing.T) {
ca0, err := minica.New()
if err != nil {
t.Fatal(err)
}
ca1, err := minica.New()
if err != nil {
t.Fatal(err)
}
ca2, err := minica.New()
if err != nil {
t.Fatal(err)
}
rootPath := t.TempDir()
writeCert := func(fn string, certs ...*x509.Certificate) error {
var b []byte
for _, crt := range certs {
b = append(b, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: crt.Raw,
})...)
}
return os.WriteFile(filepath.Join(rootPath, fn), b, 0600)
}
writeKey := func(fn string, signer crypto.Signer) error {
_, err := pemutil.Serialize(signer, pemutil.ToFile(filepath.Join(rootPath, fn), 0600))
return err
}
if err := writeCert("root0.crt", ca0.Root); err != nil {
t.Fatal(err)
}
if err := writeCert("int0.crt", ca0.Intermediate); err != nil {
t.Fatal(err)
}
if err := writeKey("int0.key", ca0.Signer); err != nil {
t.Fatal(err)
}
if err := writeCert("root1.crt", ca1.Root); err != nil {
t.Fatal(err)
}
if err := writeCert("int1.crt", ca1.Intermediate); err != nil {
t.Fatal(err)
}
if err := writeKey("int1.key", ca1.Signer); err != nil {
t.Fatal(err)
}
if err := writeCert("bundle0.crt", ca0.Root, ca1.Root); err != nil {
t.Fatal(err)
}
if err := writeCert("bundle1.crt", ca1.Root, ca2.Root); err != nil {
t.Fatal(err)
}
tests := []struct {
name string
config *config.Config
wantErr bool
}{
{"ok ca0", &config.Config{
Address: "127.0.0.1:443",
Root: []string{filepath.Join(rootPath, "root0.crt")},
IntermediateCert: filepath.Join(rootPath, "int0.crt"),
IntermediateKey: filepath.Join(rootPath, "int0.key"),
DNSNames: []string{"127.0.0.1"},
AuthorityConfig: &AuthConfig{},
}, false},
{"ok bundle", &config.Config{
Address: "127.0.0.1:443",
Root: []string{filepath.Join(rootPath, "bundle0.crt")},
IntermediateCert: filepath.Join(rootPath, "int0.crt"),
IntermediateKey: filepath.Join(rootPath, "int0.key"),
DNSNames: []string{"127.0.0.1"},
AuthorityConfig: &AuthConfig{},
}, false},
{"ok federated ca1", &config.Config{
Address: "127.0.0.1:443",
Root: []string{filepath.Join(rootPath, "root0.crt")},
FederatedRoots: []string{filepath.Join(rootPath, "root1.crt")},
IntermediateCert: filepath.Join(rootPath, "int0.crt"),
IntermediateKey: filepath.Join(rootPath, "int0.key"),
DNSNames: []string{"127.0.0.1"},
AuthorityConfig: &AuthConfig{},
}, false},
{"ok federated bundle", &config.Config{
Address: "127.0.0.1:443",
Root: []string{filepath.Join(rootPath, "root0.crt")},
FederatedRoots: []string{filepath.Join(rootPath, "bundle1.crt")},
IntermediateCert: filepath.Join(rootPath, "int0.crt"),
IntermediateKey: filepath.Join(rootPath, "int0.key"),
DNSNames: []string{"127.0.0.1"},
AuthorityConfig: &AuthConfig{},
}, false},
{"fail root", &config.Config{
Address: "127.0.0.1:443",
Root: []string{filepath.Join(rootPath, "missing.crt")},
IntermediateCert: filepath.Join(rootPath, "int0.crt"),
IntermediateKey: filepath.Join(rootPath, "int0.key"),
DNSNames: []string{"127.0.0.1"},
AuthorityConfig: &AuthConfig{},
}, true},
{"fail federated", &config.Config{
Address: "127.0.0.1:443",
Root: []string{filepath.Join(rootPath, "root0.crt")},
FederatedRoots: []string{filepath.Join(rootPath, "missing.crt")},
IntermediateCert: filepath.Join(rootPath, "int0.crt"),
IntermediateKey: filepath.Join(rootPath, "int0.key"),
DNSNames: []string{"127.0.0.1"},
AuthorityConfig: &AuthConfig{},
}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := New(tt.config)
if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
func TestAuthority_GetDatabase(t *testing.T) { func TestAuthority_GetDatabase(t *testing.T) {
auth := testAuthority(t) auth := testAuthority(t)
authWithDatabase, err := New(auth.config, WithDatabase(auth.db)) authWithDatabase, err := New(auth.config, WithDatabase(auth.db))

View file

@ -90,6 +90,7 @@ type CRLConfig struct {
GenerateOnRevoke bool `json:"generateOnRevoke,omitempty"` GenerateOnRevoke bool `json:"generateOnRevoke,omitempty"`
CacheDuration *provisioner.Duration `json:"cacheDuration,omitempty"` CacheDuration *provisioner.Duration `json:"cacheDuration,omitempty"`
RenewPeriod *provisioner.Duration `json:"renewPeriod,omitempty"` RenewPeriod *provisioner.Duration `json:"renewPeriod,omitempty"`
IDPurl string `json:"idpURL,omitempty"`
} }
// IsEnabled returns if the CRL is enabled. // IsEnabled returns if the CRL is enabled.

View file

@ -265,8 +265,20 @@ func (c *linkedCaClient) GetCertificateData(serial string) (*db.CertificateData,
ID: p.Id, Name: p.Name, Type: p.Type.String(), ID: p.Id, Name: p.Name, Type: p.Type.String(),
} }
} }
var raInfo *provisioner.RAInfo
if p := resp.RaProvisioner; p != nil && p.Provisioner != nil {
raInfo = &provisioner.RAInfo{
AuthorityID: p.AuthorityId,
ProvisionerID: p.Provisioner.Id,
ProvisionerType: p.Provisioner.Type.String(),
ProvisionerName: p.Provisioner.Name,
}
}
return &db.CertificateData{ return &db.CertificateData{
Provisioner: pd, Provisioner: pd,
RaInfo: raInfo,
}, nil }, nil
} }

View file

@ -385,16 +385,13 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
} }
var data sshutil.TemplateData var data sshutil.TemplateData
var principals []string
if claims.Email == "" { if claims.Email == "" {
// If email is empty, use the Subject claim instead to create minimal data for the template to use // If email is empty, use the Subject claim instead to create minimal
// data for the template to use.
data = sshutil.CreateTemplateData(sshutil.UserCert, claims.Subject, nil) data = sshutil.CreateTemplateData(sshutil.UserCert, claims.Subject, nil)
if v, err := unsafeParseSigned(token); err == nil { if v, err := unsafeParseSigned(token); err == nil {
data.SetToken(v) data.SetToken(v)
} }
principals = nil
} else { } else {
// Get the identity using either the default identityFunc or one injected // Get the identity using either the default identityFunc or one injected
// externally. Note that the PreferredUsername might be empty. // externally. Note that the PreferredUsername might be empty.
@ -417,8 +414,6 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
for k, v := range iden.Permissions.CriticalOptions { for k, v := range iden.Permissions.CriticalOptions {
data.AddCriticalOption(k, v) data.AddCriticalOption(k, v)
} }
principals = iden.Usernames
} }
// Use the default template unless no-templates are configured and email is // Use the default template unless no-templates are configured and email is
@ -447,7 +442,6 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
} else { } else {
signOptions = append(signOptions, sshCertOptionsValidator(SignSSHOptions{ signOptions = append(signOptions, sshCertOptionsValidator(SignSSHOptions{
CertType: SSHUserCert, CertType: SSHUserCert,
Principals: principals,
})) }))
} }

View file

@ -582,6 +582,9 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) {
{"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"name"}}, pub}, {"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"name"}}, pub},
&SignSSHOptions{CertType: "user", Principals: []string{"name", "name@smallstep.com"}, &SignSSHOptions{CertType: "user", Principals: []string{"name", "name@smallstep.com"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"ok-principals-ignore-passed", p1, args{t1, SignSSHOptions{Principals: []string{"root"}}, pub},
&SignSSHOptions{CertType: "user", Principals: []string{"name", "name@smallstep.com"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"ok-principals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{Principals: []string{"mariano"}}, pub}, {"ok-principals-getIdentity", p4, args{okGetIdentityToken, SignSSHOptions{Principals: []string{"mariano"}}, pub},
&SignSSHOptions{CertType: "user", Principals: []string{"max", "mariano"}, &SignSSHOptions{CertType: "user", Principals: []string{"max", "mariano"},
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
@ -600,7 +603,6 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) {
ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false}, ValidAfter: NewTimeDuration(tm), ValidBefore: NewTimeDuration(tm.Add(userDuration))}, http.StatusOK, false, false},
{"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true},
{"fail-user-host", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, nil, http.StatusOK, false, true}, {"fail-user-host", p1, args{t1, SignSSHOptions{CertType: "host"}, pub}, nil, http.StatusOK, false, true},
{"fail-user-principals", p1, args{t1, SignSSHOptions{Principals: []string{"root"}}, pub}, nil, http.StatusOK, false, true},
{"fail-getIdentity", p5, args{failGetIdentityToken, SignSSHOptions{}, pub}, nil, http.StatusInternalServerError, true, false}, {"fail-getIdentity", p5, args{failGetIdentityToken, SignSSHOptions{}, pub}, nil, http.StatusInternalServerError, true, false},
{"fail-sshCA-disabled", p6, args{"foo", SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, {"fail-sshCA-disabled", p6, args{"foo", SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false},
// Missing parametrs // Missing parametrs

View file

@ -773,10 +773,17 @@ func (a *Authority) GenerateCertificateRevocationList() error {
NextUpdate: now.Add(updateDuration), NextUpdate: now.Add(updateDuration),
} }
// Set CRL IDP to config item, otherwise, leave as default
var fullName string
if a.config.CRL.IDPurl != "" {
fullName = a.config.CRL.IDPurl
} else {
fullName = a.config.Audience("/1.0/crl")[0]
}
// Add distribution point. // Add distribution point.
// //
// Note that this is currently using the port 443 by default. // Note that this is currently using the port 443 by default.
fullName := a.config.Audience("/1.0/crl")[0]
if b, err := marshalDistributionPoint(fullName, false); err == nil { if b, err := marshalDistributionPoint(fullName, false); err == nil {
revocationList.ExtraExtensions = []pkix.Extension{ revocationList.ExtraExtensions = []pkix.Extension{
{Id: oidExtensionIssuingDistributionPoint, Value: b}, {Id: oidExtensionIssuingDistributionPoint, Value: b},

View file

@ -44,6 +44,9 @@ type AdminClient struct {
x5cSubject string x5cSubject string
} }
var ErrAdminAPINotImplemented = errors.New("admin API not implemented")
var ErrAdminAPINotAuthorized = errors.New("admin API not authorized")
// AdminClientError is the client side representation of an // AdminClientError is the client side representation of an
// AdminError returned by the CA. // AdminError returned by the CA.
type AdminClientError struct { type AdminClientError struct {
@ -137,6 +140,28 @@ func (c *AdminClient) retryOnError(r *http.Response) bool {
return false return false
} }
// IsEnabled checks if the admin API is enabled.
func (c *AdminClient) IsEnabled() error {
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins")})
resp, err := c.client.Get(u.String())
if err != nil {
return clientError(err)
}
defer resp.Body.Close()
if resp.StatusCode < http.StatusBadRequest {
return nil
}
switch resp.StatusCode {
case http.StatusNotFound, http.StatusNotImplemented:
return ErrAdminAPINotImplemented
case http.StatusUnauthorized:
return ErrAdminAPINotAuthorized
default:
return errors.Errorf("unexpected status code when performing is-enabled check for Admin API: %d", resp.StatusCode)
}
}
// GetAdmin performs the GET /admin/admin/{id} request to the CA. // GetAdmin performs the GET /admin/admin/{id} request to the CA.
func (c *AdminClient) GetAdmin(id string) (*linkedca.Admin, error) { func (c *AdminClient) GetAdmin(id string) (*linkedca.Admin, error) {
var retried bool var retried bool
@ -144,7 +169,7 @@ func (c *AdminClient) GetAdmin(id string) (*linkedca.Admin, error) {
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.Get(u.String())
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -227,7 +252,7 @@ func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*adminAPI.GetAdmin
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -282,7 +307,7 @@ func (c *AdminClient) CreateAdmin(createAdminRequest *adminAPI.CreateAdminReques
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -314,7 +339,7 @@ func (c *AdminClient) RemoveAdmin(id string) error {
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return errors.Wrapf(err, "client DELETE %s failed", u) return clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -340,13 +365,13 @@ func (c *AdminClient) UpdateAdmin(id string, uar *adminAPI.UpdateAdminRequest) (
} }
req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(body)) req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "create PUT %s request failed", u) return nil, errors.Wrapf(err, "create PATCH %s request failed", u)
} }
req.Header.Add("Authorization", tok) req.Header.Add("Authorization", tok)
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client PUT %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -387,13 +412,13 @@ func (c *AdminClient) GetProvisioner(opts ...ProvisionerOption) (*linkedca.Provi
} }
req, err := http.NewRequest("GET", u.String(), http.NoBody) req, err := http.NewRequest("GET", u.String(), http.NoBody)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "create PUT %s request failed", u) return nil, errors.Wrapf(err, "create GET %s request failed", u)
} }
req.Header.Add("Authorization", tok) req.Header.Add("Authorization", tok)
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -426,13 +451,13 @@ func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*admin
} }
req, err := http.NewRequest("GET", u.String(), http.NoBody) req, err := http.NewRequest("GET", u.String(), http.NoBody)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "create PUT %s request failed", u) return nil, errors.Wrapf(err, "create GET %s request failed", u)
} }
req.Header.Add("Authorization", tok) req.Header.Add("Authorization", tok)
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -502,7 +527,7 @@ func (c *AdminClient) RemoveProvisioner(opts ...ProvisionerOption) error {
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return errors.Wrapf(err, "client DELETE %s failed", u) return clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -534,7 +559,7 @@ func (c *AdminClient) CreateProvisioner(prov *linkedca.Provisioner) (*linkedca.P
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -570,7 +595,7 @@ func (c *AdminClient) UpdateProvisioner(name string, prov *linkedca.Provisioner)
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return errors.Wrapf(err, "client PUT %s failed", u) return clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -609,7 +634,7 @@ func (c *AdminClient) GetExternalAccountKeysPaginate(provisionerName, reference
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -645,7 +670,7 @@ func (c *AdminClient) CreateExternalAccountKey(provisionerName string, eakReques
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -677,7 +702,7 @@ func (c *AdminClient) RemoveExternalAccountKey(provisionerName, keyID string) er
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return errors.Wrapf(err, "client DELETE %s failed", u) return clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -704,7 +729,7 @@ func (c *AdminClient) GetAuthorityPolicy() (*linkedca.Policy, error) {
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client GET %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -739,7 +764,7 @@ func (c *AdminClient) CreateAuthorityPolicy(p *linkedca.Policy) (*linkedca.Polic
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client POST %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -774,7 +799,7 @@ func (c *AdminClient) UpdateAuthorityPolicy(p *linkedca.Policy) (*linkedca.Polic
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client PUT %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -805,7 +830,7 @@ func (c *AdminClient) RemoveAuthorityPolicy() error {
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("client DELETE %s failed: %w", u, err) return clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -832,7 +857,7 @@ func (c *AdminClient) GetProvisionerPolicy(provisionerName string) (*linkedca.Po
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client GET %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -867,7 +892,7 @@ func (c *AdminClient) CreateProvisionerPolicy(provisionerName string, p *linkedc
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client POST %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -902,7 +927,7 @@ func (c *AdminClient) UpdateProvisionerPolicy(provisionerName string, p *linkedc
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client PUT %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -933,7 +958,7 @@ func (c *AdminClient) RemoveProvisionerPolicy(provisionerName string) error {
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("client DELETE %s failed: %w", u, err) return clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -967,7 +992,7 @@ func (c *AdminClient) GetACMEPolicy(provisionerName, reference, keyID string) (*
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client GET %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -1009,7 +1034,7 @@ func (c *AdminClient) CreateACMEPolicy(provisionerName, reference, keyID string,
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client POST %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -1051,7 +1076,7 @@ func (c *AdminClient) UpdateACMEPolicy(provisionerName, reference, keyID string,
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client PUT %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -1089,7 +1114,7 @@ func (c *AdminClient) RemoveACMEPolicy(provisionerName, reference, keyID string)
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("client DELETE %s failed: %w", u, err) return clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -1120,7 +1145,7 @@ retry:
req.Header.Add("Authorization", tok) req.Header.Add("Authorization", tok)
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client POST %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -1155,7 +1180,7 @@ retry:
req.Header.Add("Authorization", tok) req.Header.Add("Authorization", tok)
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("client PUT %s failed: %w", u, err) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {
@ -1186,7 +1211,7 @@ retry:
req.Header.Add("Authorization", tok) req.Header.Add("Authorization", tok)
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("client DELETE %s failed: %w", u, err) return clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) {

View file

@ -61,7 +61,7 @@ func Bootstrap(token string) (*Client, error) {
// } // }
// resp, err := client.Get("https://internal.smallstep.com") // resp, err := client.Get("https://internal.smallstep.com")
func BootstrapClient(ctx context.Context, token string, options ...TLSOption) (*http.Client, error) { func BootstrapClient(ctx context.Context, token string, options ...TLSOption) (*http.Client, error) {
b, err := createBootstrap(token) b, err := createBootstrap(token) //nolint:contextcheck // deeply nested context; temporary
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -120,7 +120,7 @@ func BootstrapServer(ctx context.Context, token string, base *http.Server, optio
return nil, errors.New("server TLSConfig is already set") return nil, errors.New("server TLSConfig is already set")
} }
b, err := createBootstrap(token) b, err := createBootstrap(token) //nolint:contextcheck // deeply nested context; temporary
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -169,7 +169,7 @@ func BootstrapServer(ctx context.Context, token string, base *http.Server, optio
// ... // register services // ... // register services
// srv.Serve(lis) // srv.Serve(lis)
func BootstrapListener(ctx context.Context, token string, inner net.Listener, options ...TLSOption) (net.Listener, error) { func BootstrapListener(ctx context.Context, token string, inner net.Listener, options ...TLSOption) (net.Listener, error) {
b, err := createBootstrap(token) b, err := createBootstrap(token) //nolint:contextcheck // deeply nested context; temporary
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -2,6 +2,7 @@ package ca
import ( import (
"bytes" "bytes"
"context"
"crypto" "crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
@ -13,6 +14,7 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -74,18 +76,26 @@ func (c *uaClient) SetTransport(tr http.RoundTripper) {
} }
func (c *uaClient) Get(u string) (*http.Response, error) { func (c *uaClient) Get(u string) (*http.Response, error) {
req, err := http.NewRequest("GET", u, http.NoBody) return c.GetWithContext(context.Background(), u)
}
func (c *uaClient) GetWithContext(ctx context.Context, u string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, "GET", u, http.NoBody)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "new request GET %s failed", u) return nil, errors.Wrapf(err, "create GET %s request failed", u)
} }
req.Header.Set("User-Agent", UserAgent) req.Header.Set("User-Agent", UserAgent)
return c.Client.Do(req) return c.Client.Do(req)
} }
func (c *uaClient) Post(u, contentType string, body io.Reader) (*http.Response, error) { func (c *uaClient) Post(u, contentType string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest("POST", u, body) return c.PostWithContext(context.Background(), u, contentType, body)
}
func (c *uaClient) PostWithContext(ctx context.Context, u, contentType string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, "POST", u, body)
if err != nil { if err != nil {
return nil, err return nil, errors.Wrapf(err, "create POST %s request failed", u)
} }
req.Header.Set("Content-Type", contentType) req.Header.Set("Content-Type", contentType)
req.Header.Set("User-Agent", UserAgent) req.Header.Set("User-Agent", UserAgent)
@ -580,18 +590,24 @@ func (c *Client) SetTransport(tr http.RoundTripper) {
c.client.SetTransport(tr) c.client.SetTransport(tr)
} }
// Version performs the version request to the CA and returns the // Version performs the version request to the CA with an empty context and returns the
// api.VersionResponse struct. // api.VersionResponse struct.
func (c *Client) Version() (*api.VersionResponse, error) { func (c *Client) Version() (*api.VersionResponse, error) {
return c.VersionWithContext(context.Background())
}
// VersionWithContext performs the version request to the CA with the provided context
// and returns the api.VersionResponse struct.
func (c *Client) VersionWithContext(ctx context.Context) (*api.VersionResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/version"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/version"})
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.Version; client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -604,18 +620,24 @@ retry:
return &version, nil return &version, nil
} }
// Health performs the health request to the CA and returns the // Health performs the health request to the CA with an empty context
// api.HealthResponse struct. // and returns the api.HealthResponse struct.
func (c *Client) Health() (*api.HealthResponse, error) { func (c *Client) Health() (*api.HealthResponse, error) {
return c.HealthWithContext(context.Background())
}
// HealthWithContext performs the health request to the CA with the provided context
// and returns the api.HealthResponse struct.
func (c *Client) HealthWithContext(ctx context.Context) (*api.HealthResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/health"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/health"})
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.Health; client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -628,21 +650,29 @@ retry:
return &health, nil return &health, nil
} }
// Root performs the root request to the CA with the given SHA256 and returns // Root performs the root request to the CA with an empty context and the provided
// the api.RootResponse struct. It uses an insecure client, but it checks the // SHA256 and returns the api.RootResponse struct. It uses an insecure client, but
// resulting root certificate with the given SHA256, returning an error if they // it checks the resulting root certificate with the given SHA256, returning an error
// do not match. // if they do not match.
func (c *Client) Root(sha256Sum string) (*api.RootResponse, error) { func (c *Client) Root(sha256Sum string) (*api.RootResponse, error) {
return c.RootWithContext(context.Background(), sha256Sum)
}
// RootWithContext performs the root request to the CA with an empty context and the provided
// SHA256 and returns the api.RootResponse struct. It uses an insecure client, but
// it checks the resulting root certificate with the given SHA256, returning an error
// if they do not match.
func (c *Client) RootWithContext(ctx context.Context, sha256Sum string) (*api.RootResponse, error) {
var retried bool var retried bool
sha256Sum = strings.ToLower(strings.ReplaceAll(sha256Sum, "-", "")) sha256Sum = strings.ToLower(strings.ReplaceAll(sha256Sum, "-", ""))
u := c.endpoint.ResolveReference(&url.URL{Path: "/root/" + sha256Sum}) u := c.endpoint.ResolveReference(&url.URL{Path: "/root/" + sha256Sum})
retry: retry:
resp, err := newInsecureClient().Get(u.String()) resp, err := newInsecureClient().GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.Root; client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -660,9 +690,15 @@ retry:
return &root, nil return &root, nil
} }
// Sign performs the sign request to the CA and returns the api.SignResponse // Sign performs the sign request to the CA with an empty context and returns
// struct. // the api.SignResponse struct.
func (c *Client) Sign(req *api.SignRequest) (*api.SignResponse, error) { func (c *Client) Sign(req *api.SignRequest) (*api.SignResponse, error) {
return c.SignWithContext(context.Background(), req)
}
// SignWithContext performs the sign request to the CA with the provided context
// and returns the api.SignResponse struct.
func (c *Client) SignWithContext(ctx context.Context, req *api.SignRequest) (*api.SignResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
@ -670,12 +706,12 @@ func (c *Client) Sign(req *api.SignRequest) (*api.SignResponse, error) {
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/sign"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/sign"})
retry: retry:
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) resp, err := c.client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.Sign; client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -691,19 +727,30 @@ retry:
return &sign, nil return &sign, nil
} }
// Renew performs the renew request to the CA and returns the api.SignResponse // Renew performs the renew request to the CA with an empty context and
// struct. // returns the api.SignResponse struct.
func (c *Client) Renew(tr http.RoundTripper) (*api.SignResponse, error) { func (c *Client) Renew(tr http.RoundTripper) (*api.SignResponse, error) {
return c.RenewWithContext(context.Background(), tr)
}
// RenewWithContext performs the renew request to the CA with the provided context
// and returns the api.SignResponse struct.
func (c *Client) RenewWithContext(ctx context.Context, tr http.RoundTripper) (*api.SignResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"})
client := &http.Client{Transport: tr} client := &http.Client{Transport: tr}
retry: retry:
resp, err := client.Post(u.String(), "application/json", http.NoBody) req, err := http.NewRequestWithContext(ctx, "POST", u.String(), http.NoBody)
if err != nil { if err != nil {
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.Renew; client POST %s failed", u) return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -717,23 +764,30 @@ retry:
} }
// RenewWithToken performs the renew request to the CA with the given // RenewWithToken performs the renew request to the CA with the given
// authorization token and returns the api.SignResponse struct. This method is // authorization token and and empty context and returns the api.SignResponse struct.
// generally used to renew an expired certificate. // This method is generally used to renew an expired certificate.
func (c *Client) RenewWithToken(token string) (*api.SignResponse, error) { func (c *Client) RenewWithToken(token string) (*api.SignResponse, error) {
return c.RenewWithTokenAndContext(context.Background(), token)
}
// RenewWithTokenAndContext performs the renew request to the CA with the given
// authorization token and context and returns the api.SignResponse struct.
// This method is generally used to renew an expired certificate.
func (c *Client) RenewWithTokenAndContext(ctx context.Context, token string) (*api.SignResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"})
req, err := http.NewRequest("POST", u.String(), http.NoBody) req, err := http.NewRequestWithContext(ctx, "POST", u.String(), http.NoBody)
if err != nil { if err != nil {
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.RenewWithToken; error creating request") return nil, errors.Wrapf(err, "create POST %s request failed", u)
} }
req.Header.Add("Authorization", "Bearer "+token) req.Header.Add("Authorization", "Bearer "+token)
retry: retry:
resp, err := c.client.Do(req) resp, err := c.client.Do(req)
if err != nil { if err != nil {
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.RenewWithToken; client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -746,24 +800,34 @@ retry:
return &sign, nil return &sign, nil
} }
// Rekey performs the rekey request to the CA and returns the api.SignResponse // Rekey performs the rekey request to the CA with an empty context and
// struct. // returns the api.SignResponse struct.
func (c *Client) Rekey(req *api.RekeyRequest, tr http.RoundTripper) (*api.SignResponse, error) { func (c *Client) Rekey(req *api.RekeyRequest, tr http.RoundTripper) (*api.SignResponse, error) {
return c.RekeyWithContext(context.Background(), req, tr)
}
// RekeyWithContext performs the rekey request to the CA with the provided context
// and returns the api.SignResponse struct.
func (c *Client) RekeyWithContext(ctx context.Context, req *api.RekeyRequest, tr http.RoundTripper) (*api.SignResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error marshaling request") return nil, errors.Wrap(err, "error marshaling request")
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/rekey"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/rekey"})
client := &http.Client{Transport: tr} client := &http.Client{Transport: tr}
retry: retry:
resp, err := client.Post(u.String(), "application/json", bytes.NewReader(body)) httpReq, err := http.NewRequestWithContext(ctx, "POST", u.String(), bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.Rekey; client POST %s failed", u) return nil, err
}
httpReq.Header.Set("Content-Type", "application/json")
resp, err := client.Do(httpReq)
if err != nil {
return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -776,9 +840,15 @@ retry:
return &sign, nil return &sign, nil
} }
// Revoke performs the revoke request to the CA and returns the api.RevokeResponse // Revoke performs the revoke request to the CA with an empty context and returns
// struct. // the api.RevokeResponse struct.
func (c *Client) Revoke(req *api.RevokeRequest, tr http.RoundTripper) (*api.RevokeResponse, error) { func (c *Client) Revoke(req *api.RevokeRequest, tr http.RoundTripper) (*api.RevokeResponse, error) {
return c.RevokeWithContext(context.Background(), req, tr)
}
// RevokeWithContext performs the revoke request to the CA with the provided context and
// returns the api.RevokeResponse struct.
func (c *Client) RevokeWithContext(ctx context.Context, req *api.RevokeRequest, tr http.RoundTripper) (*api.RevokeResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
@ -793,12 +863,12 @@ retry:
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/revoke"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/revoke"})
resp, err := client.Post(u.String(), "application/json", bytes.NewReader(body)) resp, err := client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -811,12 +881,21 @@ retry:
return &revoke, nil return &revoke, nil
} }
// Provisioners performs the provisioners request to the CA and returns the // Provisioners performs the provisioners request to the CA with an empty context
// api.ProvisionersResponse struct with a map of provisioners. // and returns the api.ProvisionersResponse struct with a map of provisioners.
// //
// ProvisionerOption WithProvisionerCursor and WithProvisionLimit can be used to // ProvisionerOption WithProvisionerCursor and WithProvisionLimit can be used to
// paginate the provisioners. // paginate the provisioners.
func (c *Client) Provisioners(opts ...ProvisionerOption) (*api.ProvisionersResponse, error) { func (c *Client) Provisioners(opts ...ProvisionerOption) (*api.ProvisionersResponse, error) {
return c.ProvisionersWithContext(context.Background(), opts...)
}
// ProvisionersWithContext performs the provisioners request to the CA with the provided context
// and returns the api.ProvisionersResponse struct with a map of provisioners.
//
// ProvisionerOption WithProvisionerCursor and WithProvisionLimit can be used to
// paginate the provisioners.
func (c *Client) ProvisionersWithContext(ctx context.Context, opts ...ProvisionerOption) (*api.ProvisionersResponse, error) {
var retried bool var retried bool
o := new(ProvisionerOptions) o := new(ProvisionerOptions)
if err := o.Apply(opts); err != nil { if err := o.Apply(opts); err != nil {
@ -827,12 +906,12 @@ func (c *Client) Provisioners(opts ...ProvisionerOption) (*api.ProvisionersRespo
RawQuery: o.rawQuery(), RawQuery: o.rawQuery(),
}) })
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -845,19 +924,26 @@ retry:
return &provisioners, nil return &provisioners, nil
} }
// ProvisionerKey performs the request to the CA to get the encrypted key for // ProvisionerKey performs the request to the CA with an empty context to get
// the given provisioner kid and returns the api.ProvisionerKeyResponse struct // the encrypted key for the given provisioner kid and returns the api.ProvisionerKeyResponse
// with the encrypted key. // struct with the encrypted key.
func (c *Client) ProvisionerKey(kid string) (*api.ProvisionerKeyResponse, error) { func (c *Client) ProvisionerKey(kid string) (*api.ProvisionerKeyResponse, error) {
return c.ProvisionerKeyWithContext(context.Background(), kid)
}
// ProvisionerKeyWithContext performs the request to the CA with the provided context to get
// the encrypted key for the given provisioner kid and returns the api.ProvisionerKeyResponse
// struct with the encrypted key.
func (c *Client) ProvisionerKeyWithContext(ctx context.Context, kid string) (*api.ProvisionerKeyResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/provisioners/" + kid + "/encrypted-key"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/provisioners/" + kid + "/encrypted-key"})
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -870,18 +956,24 @@ retry:
return &key, nil return &key, nil
} }
// Roots performs the get roots request to the CA and returns the // Roots performs the get roots request to the CA with an empty context
// api.RootsResponse struct. // and returns the api.RootsResponse struct.
func (c *Client) Roots() (*api.RootsResponse, error) { func (c *Client) Roots() (*api.RootsResponse, error) {
return c.RootsWithContext(context.Background())
}
// RootsWithContext performs the get roots request to the CA with the provided context
// and returns the api.RootsResponse struct.
func (c *Client) RootsWithContext(ctx context.Context) (*api.RootsResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/roots"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/roots"})
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -894,18 +986,24 @@ retry:
return &roots, nil return &roots, nil
} }
// Federation performs the get federation request to the CA and returns the // Federation performs the get federation request to the CA with an empty context
// api.FederationResponse struct. // and returns the api.FederationResponse struct.
func (c *Client) Federation() (*api.FederationResponse, error) { func (c *Client) Federation() (*api.FederationResponse, error) {
return c.FederationWithContext(context.Background())
}
// FederationWithContext performs the get federation request to the CA with the provided context
// and returns the api.FederationResponse struct.
func (c *Client) FederationWithContext(ctx context.Context) (*api.FederationResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/federation"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/federation"})
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -918,9 +1016,15 @@ retry:
return &federation, nil return &federation, nil
} }
// SSHSign performs the POST /ssh/sign request to the CA and returns the // SSHSign performs the POST /ssh/sign request to the CA with an empty context
// api.SSHSignResponse struct. // and returns the api.SSHSignResponse struct.
func (c *Client) SSHSign(req *api.SSHSignRequest) (*api.SSHSignResponse, error) { func (c *Client) SSHSign(req *api.SSHSignRequest) (*api.SSHSignResponse, error) {
return c.SSHSignWithContext(context.Background(), req)
}
// SSHSignWithContext performs the POST /ssh/sign request to the CA with the provided context
// and returns the api.SSHSignResponse struct.
func (c *Client) SSHSignWithContext(ctx context.Context, req *api.SSHSignRequest) (*api.SSHSignResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
@ -928,12 +1032,12 @@ func (c *Client) SSHSign(req *api.SSHSignRequest) (*api.SSHSignResponse, error)
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/sign"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/sign"})
retry: retry:
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) resp, err := c.client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -946,9 +1050,15 @@ retry:
return &sign, nil return &sign, nil
} }
// SSHRenew performs the POST /ssh/renew request to the CA and returns the // SSHRenew performs the POST /ssh/renew request to the CA with an empty context
// api.SSHRenewResponse struct. // and returns the api.SSHRenewResponse struct.
func (c *Client) SSHRenew(req *api.SSHRenewRequest) (*api.SSHRenewResponse, error) { func (c *Client) SSHRenew(req *api.SSHRenewRequest) (*api.SSHRenewResponse, error) {
return c.SSHRenewWithContext(context.Background(), req)
}
// SSHRenewWithContext performs the POST /ssh/renew request to the CA with the provided context
// and returns the api.SSHRenewResponse struct.
func (c *Client) SSHRenewWithContext(ctx context.Context, req *api.SSHRenewRequest) (*api.SSHRenewResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
@ -956,12 +1066,12 @@ func (c *Client) SSHRenew(req *api.SSHRenewRequest) (*api.SSHRenewResponse, erro
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/renew"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/renew"})
retry: retry:
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) resp, err := c.client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -974,9 +1084,15 @@ retry:
return &renew, nil return &renew, nil
} }
// SSHRekey performs the POST /ssh/rekey request to the CA and returns the // SSHRekey performs the POST /ssh/rekey request to the CA with an empty context
// api.SSHRekeyResponse struct. // and returns the api.SSHRekeyResponse struct.
func (c *Client) SSHRekey(req *api.SSHRekeyRequest) (*api.SSHRekeyResponse, error) { func (c *Client) SSHRekey(req *api.SSHRekeyRequest) (*api.SSHRekeyResponse, error) {
return c.SSHRekeyWithContext(context.Background(), req)
}
// SSHRekeyWithContext performs the POST /ssh/rekey request to the CA with the provided context
// and returns the api.SSHRekeyResponse struct.
func (c *Client) SSHRekeyWithContext(ctx context.Context, req *api.SSHRekeyRequest) (*api.SSHRekeyResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
@ -984,12 +1100,12 @@ func (c *Client) SSHRekey(req *api.SSHRekeyRequest) (*api.SSHRekeyResponse, erro
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/rekey"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/rekey"})
retry: retry:
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) resp, err := c.client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -1002,9 +1118,15 @@ retry:
return &rekey, nil return &rekey, nil
} }
// SSHRevoke performs the POST /ssh/revoke request to the CA and returns the // SSHRevoke performs the POST /ssh/revoke request to the CA with an empty context
// api.SSHRevokeResponse struct. // and returns the api.SSHRevokeResponse struct.
func (c *Client) SSHRevoke(req *api.SSHRevokeRequest) (*api.SSHRevokeResponse, error) { func (c *Client) SSHRevoke(req *api.SSHRevokeRequest) (*api.SSHRevokeResponse, error) {
return c.SSHRevokeWithContext(context.Background(), req)
}
// SSHRevokeWithContext performs the POST /ssh/revoke request to the CA with the provided context
// and returns the api.SSHRevokeResponse struct.
func (c *Client) SSHRevokeWithContext(ctx context.Context, req *api.SSHRevokeRequest) (*api.SSHRevokeResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
@ -1012,12 +1134,12 @@ func (c *Client) SSHRevoke(req *api.SSHRevokeRequest) (*api.SSHRevokeResponse, e
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/revoke"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/revoke"})
retry: retry:
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) resp, err := c.client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -1030,18 +1152,24 @@ retry:
return &revoke, nil return &revoke, nil
} }
// SSHRoots performs the GET /ssh/roots request to the CA and returns the // SSHRoots performs the GET /ssh/roots request to the CA with an empty context
// api.SSHRootsResponse struct. // and returns the api.SSHRootsResponse struct.
func (c *Client) SSHRoots() (*api.SSHRootsResponse, error) { func (c *Client) SSHRoots() (*api.SSHRootsResponse, error) {
return c.SSHRootsWithContext(context.Background())
}
// SSHRootsWithContext performs the GET /ssh/roots request to the CA with the provided context
// and returns the api.SSHRootsResponse struct.
func (c *Client) SSHRootsWithContext(ctx context.Context) (*api.SSHRootsResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/roots"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/roots"})
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -1054,18 +1182,24 @@ retry:
return &keys, nil return &keys, nil
} }
// SSHFederation performs the get /ssh/federation request to the CA and returns // SSHFederation performs the get /ssh/federation request to the CA with an empty context
// the api.SSHRootsResponse struct. // and returns the api.SSHRootsResponse struct.
func (c *Client) SSHFederation() (*api.SSHRootsResponse, error) { func (c *Client) SSHFederation() (*api.SSHRootsResponse, error) {
return c.SSHFederationWithContext(context.Background())
}
// SSHFederationWithContext performs the get /ssh/federation request to the CA with the provided context
// and returns the api.SSHRootsResponse struct.
func (c *Client) SSHFederationWithContext(ctx context.Context) (*api.SSHRootsResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/federation"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/federation"})
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -1078,9 +1212,15 @@ retry:
return &keys, nil return &keys, nil
} }
// SSHConfig performs the POST /ssh/config request to the CA to get the ssh // SSHConfig performs the POST /ssh/config request to the CA with an empty context
// configuration templates. // to get the ssh configuration templates.
func (c *Client) SSHConfig(req *api.SSHConfigRequest) (*api.SSHConfigResponse, error) { func (c *Client) SSHConfig(req *api.SSHConfigRequest) (*api.SSHConfigResponse, error) {
return c.SSHConfigWithContext(context.Background(), req)
}
// SSHConfigWithContext performs the POST /ssh/config request to the CA with the provided context
// to get the ssh configuration templates.
func (c *Client) SSHConfigWithContext(ctx context.Context, req *api.SSHConfigRequest) (*api.SSHConfigResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
@ -1088,12 +1228,12 @@ func (c *Client) SSHConfig(req *api.SSHConfigRequest) (*api.SSHConfigResponse, e
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/config"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/config"})
retry: retry:
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) resp, err := c.client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -1106,9 +1246,15 @@ retry:
return &cfg, nil return &cfg, nil
} }
// SSHCheckHost performs the POST /ssh/check-host request to the CA with the // SSHCheckHost performs the POST /ssh/check-host request to the CA with an empty context,
// given principal. // the principal and a token and returns the api.SSHCheckPrincipalResponse.
func (c *Client) SSHCheckHost(principal, token string) (*api.SSHCheckPrincipalResponse, error) { func (c *Client) SSHCheckHost(principal, token string) (*api.SSHCheckPrincipalResponse, error) {
return c.SSHCheckHostWithContext(context.Background(), principal, token)
}
// SSHCheckHostWithContext performs the POST /ssh/check-host request to the CA with the provided context,
// principal and token and returns the api.SSHCheckPrincipalResponse.
func (c *Client) SSHCheckHostWithContext(ctx context.Context, principal, token string) (*api.SSHCheckPrincipalResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(&api.SSHCheckPrincipalRequest{ body, err := json.Marshal(&api.SSHCheckPrincipalRequest{
Type: provisioner.SSHHostCert, Type: provisioner.SSHHostCert,
@ -1121,13 +1267,12 @@ func (c *Client) SSHCheckHost(principal, token string) (*api.SSHCheckPrincipalRe
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/check-host"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/check-host"})
retry: retry:
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) resp, err := c.client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client POST %s failed", return nil, clientError(err)
[]interface{}{u, errs.WithMessage("Failed to perform POST request to %s", u)}...)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -1141,17 +1286,22 @@ retry:
return &check, nil return &check, nil
} }
// SSHGetHosts performs the GET /ssh/get-hosts request to the CA. // SSHGetHosts performs the GET /ssh/get-hosts request to the CA with an empty context.
func (c *Client) SSHGetHosts() (*api.SSHGetHostsResponse, error) { func (c *Client) SSHGetHosts() (*api.SSHGetHostsResponse, error) {
return c.SSHGetHostsWithContext(context.Background())
}
// SSHGetHostsWithContext performs the GET /ssh/get-hosts request to the CA with the provided context.
func (c *Client) SSHGetHostsWithContext(ctx context.Context) (*api.SSHGetHostsResponse, error) {
var retried bool var retried bool
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/hosts"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/hosts"})
retry: retry:
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client GET %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -1164,8 +1314,13 @@ retry:
return &hosts, nil return &hosts, nil
} }
// SSHBastion performs the POST /ssh/bastion request to the CA. // SSHBastion performs the POST /ssh/bastion request to the CA with an empty context.
func (c *Client) SSHBastion(req *api.SSHBastionRequest) (*api.SSHBastionResponse, error) { func (c *Client) SSHBastion(req *api.SSHBastionRequest) (*api.SSHBastionResponse, error) {
return c.SSHBastionWithContext(context.Background(), req)
}
// SSHBastionWithContext performs the POST /ssh/bastion request to the CA with the provided context.
func (c *Client) SSHBastionWithContext(ctx context.Context, req *api.SSHBastionRequest) (*api.SSHBastionResponse, error) {
var retried bool var retried bool
body, err := json.Marshal(req) body, err := json.Marshal(req)
if err != nil { if err != nil {
@ -1173,12 +1328,12 @@ func (c *Client) SSHBastion(req *api.SSHBastionRequest) (*api.SSHBastionResponse
} }
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/bastion"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/bastion"})
retry: retry:
resp, err := c.client.Post(u.String(), "application/json", bytes.NewReader(body)) resp, err := c.client.PostWithContext(ctx, u.String(), "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "client.SSHBastion; client POST %s failed", u) return nil, clientError(err)
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
if !retried && c.retryOnError(resp) { if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
retried = true retried = true
goto retry goto retry
} }
@ -1192,13 +1347,18 @@ retry:
} }
// RootFingerprint is a helper method that returns the current root fingerprint. // RootFingerprint is a helper method that returns the current root fingerprint.
// It does an health connection and gets the fingerprint from the TLS verified // It does an health connection and gets the fingerprint from the TLS verified chains.
// chains.
func (c *Client) RootFingerprint() (string, error) { func (c *Client) RootFingerprint() (string, error) {
return c.RootFingerprintWithContext(context.Background())
}
// RootFingerprintWithContext is a helper method that returns the current root fingerprint.
// It does an health connection and gets the fingerprint from the TLS verified chains.
func (c *Client) RootFingerprintWithContext(ctx context.Context) (string, error) {
u := c.endpoint.ResolveReference(&url.URL{Path: "/health"}) u := c.endpoint.ResolveReference(&url.URL{Path: "/health"})
resp, err := c.client.Get(u.String()) resp, err := c.client.GetWithContext(ctx, u.String())
if err != nil { if err != nil {
return "", errors.Wrapf(err, "client GET %s failed", u) return "", clientError(err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.TLS == nil || len(resp.TLS.VerifiedChains) == 0 { if resp.TLS == nil || len(resp.TLS.VerifiedChains) == 0 {
@ -1353,3 +1513,12 @@ func readError(r io.ReadCloser) error {
} }
return apiErr return apiErr
} }
func clientError(err error) error {
var uerr *url.Error
if errors.As(err, &uerr) {
return fmt.Errorf("client %s %s failed: %w",
strings.ToUpper(uerr.Op), uerr.URL, uerr.Err)
}
return fmt.Errorf("client request failed: %w", err)
}

View file

@ -24,6 +24,10 @@ import (
// getDefaultTransport. // getDefaultTransport.
var mTLSDialContext func() func(ctx context.Context, network, address string) (net.Conn, error) var mTLSDialContext func() func(ctx context.Context, network, address string) (net.Conn, error)
// localAddr is the local address to use when dialing an address. This address
// is defined by the environment variable STEP_CLIENT_ADDR.
var localAddr net.Addr
func init() { func init() {
// STEP_TLS_TUNNEL is an environment variable that can be set to do an TLS // STEP_TLS_TUNNEL is an environment variable that can be set to do an TLS
// over (m)TLS tunnel to step-ca using identity-like credentials. The value // over (m)TLS tunnel to step-ca using identity-like credentials. The value
@ -70,6 +74,29 @@ func init() {
} }
} }
} }
// STEP_CLIENT_ADDR is an environment variable that can be set to define the
// local address to use when dialing an address. This can be useful when
// step is run behind a CIDR-based ACL.
//
// STEP_CLIENT_ADDR can be set to an IP ("127.0.0.1", "[::1]"), a hostname
// ("localhost"), or a host:port ("[::1]:0"). If the port is set to
// something other than ":0" and the dialer is created multiple times it
// will fail with an "address already in use" error.
//
// See https://github.com/smallstep/cli/issues/730
if v := os.Getenv("STEP_CLIENT_ADDR"); v != "" {
_, _, err := net.SplitHostPort(v)
if err != nil {
// assuming that the error is a missing port, if it's not it will
// panic below.
v += ":0"
}
localAddr, err = net.ResolveTCPAddr("tcp", v)
if err != nil {
panic(err)
}
}
} }
// GetClientTLSConfig returns a tls.Config for client use configured with the // GetClientTLSConfig returns a tls.Config for client use configured with the
@ -108,7 +135,7 @@ func (c *Client) getClientTLSConfig(ctx context.Context, sign *api.SignResponse,
//nolint:staticcheck // Use mutable tls.Config on renew //nolint:staticcheck // Use mutable tls.Config on renew
tr.DialTLS = c.buildDialTLS(tlsCtx) tr.DialTLS = c.buildDialTLS(tlsCtx)
// tr.DialTLSContext = c.buildDialTLSContext(tlsCtx) // tr.DialTLSContext = c.buildDialTLSContext(tlsCtx)
renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) //nolint:contextcheck // deeply nested context
// Update client transport // Update client transport
c.SetTransport(tr) c.SetTransport(tr)
@ -156,7 +183,7 @@ func (c *Client) GetServerTLSConfig(ctx context.Context, sign *api.SignResponse,
//nolint:staticcheck // Use mutable tls.Config on renew //nolint:staticcheck // Use mutable tls.Config on renew
tr.DialTLS = c.buildDialTLS(tlsCtx) tr.DialTLS = c.buildDialTLS(tlsCtx)
// tr.DialTLSContext = c.buildDialTLSContext(tlsCtx) // tr.DialTLSContext = c.buildDialTLSContext(tlsCtx)
renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) renewer.RenewCertificate = getRenewFunc(tlsCtx, c, tr, pk) //nolint:contextcheck // deeply nested context
// Update client transport // Update client transport
c.SetTransport(tr) c.SetTransport(tr)
@ -280,6 +307,7 @@ func getDefaultDialer() *net.Dialer {
// With the KeepAlive parameter set to 0, it will be use Golang's default. // With the KeepAlive parameter set to 0, it will be use Golang's default.
return &net.Dialer{ return &net.Dialer{
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
LocalAddr: localAddr,
} }
} }

View file

@ -1,6 +1,7 @@
package stepcas package stepcas
import ( import (
"context"
"net/url" "net/url"
"strings" "strings"
"time" "time"
@ -37,7 +38,7 @@ type stepIssuer interface {
} }
// newStepIssuer returns the configured step issuer. // newStepIssuer returns the configured step issuer.
func newStepIssuer(caURL *url.URL, client *ca.Client, iss *apiv1.CertificateIssuer) (stepIssuer, error) { func newStepIssuer(ctx context.Context, caURL *url.URL, client *ca.Client, iss *apiv1.CertificateIssuer) (stepIssuer, error) {
if err := validateCertificateIssuer(iss); err != nil { if err := validateCertificateIssuer(iss); err != nil {
return nil, err return nil, err
} }
@ -46,7 +47,7 @@ func newStepIssuer(caURL *url.URL, client *ca.Client, iss *apiv1.CertificateIssu
case "x5c": case "x5c":
return newX5CIssuer(caURL, iss) return newX5CIssuer(caURL, iss)
case "jwk": case "jwk":
return newJWKIssuer(caURL, client, iss) return newJWKIssuer(ctx, caURL, client, iss)
default: default:
return nil, errors.Errorf("stepCAS `certificateIssuer.type` %s is not supported", iss.Type) return nil, errors.Errorf("stepCAS `certificateIssuer.type` %s is not supported", iss.Type)
} }

View file

@ -1,6 +1,7 @@
package stepcas package stepcas
import ( import (
"context"
"net/url" "net/url"
"reflect" "reflect"
"testing" "testing"
@ -118,7 +119,7 @@ func Test_newStepIssuer(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := newStepIssuer(tt.args.caURL, tt.args.client, tt.args.iss) got, err := newStepIssuer(context.TODO(), tt.args.caURL, tt.args.client, tt.args.iss)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("newStepIssuer() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("newStepIssuer() error = %v, wantErr %v", err, tt.wantErr)
return return

View file

@ -1,6 +1,7 @@
package stepcas package stepcas
import ( import (
"context"
"crypto" "crypto"
"encoding/json" "encoding/json"
"net/url" "net/url"
@ -21,13 +22,13 @@ type jwkIssuer struct {
signer jose.Signer signer jose.Signer
} }
func newJWKIssuer(caURL *url.URL, client *ca.Client, cfg *apiv1.CertificateIssuer) (*jwkIssuer, error) { func newJWKIssuer(ctx context.Context, caURL *url.URL, client *ca.Client, cfg *apiv1.CertificateIssuer) (*jwkIssuer, error) {
var err error var err error
var signer jose.Signer var signer jose.Signer
// Read the key from the CA if not provided. // Read the key from the CA if not provided.
// Or read it from a PEM file. // Or read it from a PEM file.
if cfg.Key == "" { if cfg.Key == "" {
p, err := findProvisioner(client, provisioner.TypeJWK, cfg.Provisioner) p, err := findProvisioner(ctx, client, provisioner.TypeJWK, cfg.Provisioner)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -144,10 +145,10 @@ func newJWKSignerFromEncryptedKey(kid, key, password string) (jose.Signer, error
return newJoseSigner(signer, so) return newJoseSigner(signer, so)
} }
func findProvisioner(client *ca.Client, typ provisioner.Type, name string) (provisioner.Interface, error) { func findProvisioner(ctx context.Context, client *ca.Client, typ provisioner.Type, name string) (provisioner.Interface, error) {
cursor := "" cursor := ""
for { for {
ps, err := client.Provisioners(ca.WithProvisionerCursor(cursor)) ps, err := client.ProvisionersWithContext(ctx, ca.WithProvisionerCursor(cursor))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -43,7 +43,7 @@ func New(ctx context.Context, opts apiv1.Options) (*StepCAS, error) {
} }
// Create client. // Create client.
client, err := ca.NewClient(opts.CertificateAuthority, ca.WithRootSHA256(opts.CertificateAuthorityFingerprint)) client, err := ca.NewClient(opts.CertificateAuthority, ca.WithRootSHA256(opts.CertificateAuthorityFingerprint)) //nolint:contextcheck // deeply nested context
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -52,7 +52,7 @@ func New(ctx context.Context, opts apiv1.Options) (*StepCAS, error) {
// Create configured issuer unless we only want to use GetCertificateAuthority. // Create configured issuer unless we only want to use GetCertificateAuthority.
// This avoid the request for the password if not provided. // This avoid the request for the password if not provided.
if !opts.IsCAGetter { if !opts.IsCAGetter {
if iss, err = newStepIssuer(caURL, client, opts.CertificateIssuer); err != nil { if iss, err = newStepIssuer(ctx, caURL, client, opts.CertificateIssuer); err != nil {
return nil, err return nil, err
} }
} }

View file

@ -245,7 +245,7 @@ func testJWKIssuer(t *testing.T, caURL *url.URL, password string) *jwkIssuer {
key = testEncryptedKeyPath key = testEncryptedKeyPath
password = testPassword password = testPassword
} }
jwk, err := newJWKIssuer(caURL, client, &apiv1.CertificateIssuer{ jwk, err := newJWKIssuer(context.TODO(), caURL, client, &apiv1.CertificateIssuer{
Type: "jwk", Type: "jwk",
Provisioner: "ra@doe.org", Provisioner: "ra@doe.org",
Key: key, Key: key,

View file

@ -121,9 +121,15 @@ func appAction(ctx *cli.Context) error {
// Allow custom contexts. // Allow custom contexts.
if caCtx := ctx.String("context"); caCtx != "" { if caCtx := ctx.String("context"); caCtx != "" {
if _, ok := step.Contexts().Get(caCtx); ok {
if err := step.Contexts().SetCurrent(caCtx); err != nil { if err := step.Contexts().SetCurrent(caCtx); err != nil {
return err return err
} }
} else if token == "" {
return fmt.Errorf("context %q not found", caCtx)
} else if err := createContext(caCtx); err != nil {
return err
}
} }
var configFile string var configFile string
@ -234,6 +240,26 @@ To get a linked authority token:
return nil return nil
} }
// createContext creates a new context using the given name for the context,
// authority and profile.
func createContext(name string) error {
if err := step.Contexts().Add(&step.Context{
Name: name, Authority: name, Profile: name,
}); err != nil {
return fmt.Errorf("error adding context: %w", err)
}
if err := step.Contexts().SaveCurrent(name); err != nil {
return fmt.Errorf("error saving context: %w", err)
}
if err := step.Contexts().SetCurrent(name); err != nil {
return fmt.Errorf("error setting context: %w", err)
}
if err := os.MkdirAll(step.Path(), 0700); err != nil {
return fmt.Errorf("error creating directory: %w", err)
}
return nil
}
// fatal writes the passed error on the standard error and exits with the exit // fatal writes the passed error on the standard error and exits with the exit
// code 1. If the environment variable STEPDEBUG is set to 1 it shows the // code 1. If the environment variable STEPDEBUG is set to 1 it shows the
// stack trace of the error. // stack trace of the error.

View file

@ -7,7 +7,6 @@ RUN apk add --no-cache curl git make
RUN make V=1 download RUN make V=1 download
RUN make V=1 bin/step-ca bin/step-awskms-init bin/step-cloudkms-init RUN make V=1 bin/step-ca bin/step-awskms-init bin/step-cloudkms-init
FROM smallstep/step-cli:latest FROM smallstep/step-cli:latest
COPY --from=builder /src/bin/step-ca /usr/local/bin/step-ca COPY --from=builder /src/bin/step-ca /usr/local/bin/step-ca

View file

@ -34,22 +34,43 @@ function generate_password () {
# Initialize a CA if not already initialized # Initialize a CA if not already initialized
function step_ca_init () { function step_ca_init () {
DOCKER_STEPCA_INIT_PROVISIONER_NAME="${DOCKER_STEPCA_INIT_PROVISIONER_NAME:-admin}"
DOCKER_STEPCA_INIT_ADMIN_SUBJECT="${DOCKER_STEPCA_INIT_ADMIN_SUBJECT:-step}"
local -a setup_args=( local -a setup_args=(
--name "${DOCKER_STEPCA_INIT_NAME}" --name "${DOCKER_STEPCA_INIT_NAME}"
--dns "${DOCKER_STEPCA_INIT_DNS_NAMES}" --dns "${DOCKER_STEPCA_INIT_DNS_NAMES}"
--provisioner "${DOCKER_STEPCA_INIT_PROVISIONER_NAME:-admin}" --provisioner "${DOCKER_STEPCA_INIT_PROVISIONER_NAME}"
--password-file "${STEPPATH}/password" --password-file "${STEPPATH}/password"
--provisioner-password-file "${STEPPATH}/provisioner_password"
--address ":9000" --address ":9000"
) )
if [ -n "${DOCKER_STEPCA_INIT_PASSWORD}" ]; then if [ -n "${DOCKER_STEPCA_INIT_PASSWORD}" ]; then
echo "${DOCKER_STEPCA_INIT_PASSWORD}" > "${STEPPATH}/password" echo "${DOCKER_STEPCA_INIT_PASSWORD}" > "${STEPPATH}/password"
echo "${DOCKER_STEPCA_INIT_PASSWORD}" > "${STEPPATH}/provisioner_password"
else else
generate_password > "${STEPPATH}/password" generate_password > "${STEPPATH}/password"
generate_password > "${STEPPATH}/provisioner_password"
fi fi
if [ -n "${DOCKER_STEPCA_INIT_SSH}" ]; then if [ "${DOCKER_STEPCA_INIT_SSH}" == "true" ]; then
setup_args=("${setup_args[@]}" --ssh) setup_args=("${setup_args[@]}" --ssh)
fi fi
if [ "${DOCKER_STEPCA_INIT_ACME}" == "true" ]; then
setup_args=("${setup_args[@]}" --acme)
fi
if [ "${DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT}" == "true" ]; then
setup_args=("${setup_args[@]}" --remote-management
--admin-subject "${DOCKER_STEPCA_INIT_ADMIN_SUBJECT}"
)
fi
step ca init "${setup_args[@]}" step ca init "${setup_args[@]}"
echo ""
if [ "${DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT}" == "true" ]; then
echo "👉 Your CA administrative username is: ${DOCKER_STEPCA_INIT_ADMIN_SUBJECT}"
fi
echo "👉 Your CA administrative password is: $(< $STEPPATH/provisioner_password )"
echo "🤫 This will only be displayed once."
shred -u $STEPPATH/provisioner_password
mv $STEPPATH/password $PWDPATH mv $STEPPATH/password $PWDPATH
} }

View file

@ -1,740 +0,0 @@
# Getting Started
Demonstrates setting up your own public key infrastructure (PKI) and certificate authority (CA) using `step ca`
and getting certificates using the `step` command line tool and SDK.
Check out [Getting started with docker](docker.md) to run [step certificates](https://github.com/smallstep/certificates)
using docker.
![Animated terminal showing step ca init in practice](https://smallstep.com/images/blog/2018-12-04-unfurl.gif)
## Prerequisites
* [Step CA](#installation-guide)
## Terminology
### PKI - Public Key Infrastructure
A set of roles, policies, and procedures needed to create, manage, distribute,
use, store, and revoke digital certificates and manage public-key encryption.
The purpose of a PKI is to facilitate the secure electronic transfer of
information for a range of network activities.
### Provisioners
Provisioners are people or code that are registered with the CA and authorized
to issue "provisioning tokens". Provisioning tokens are single use tokens that
can be used to authenticate with the CA and get a certificate.
See [provisioners.md](provisioners.md) for more information on the supported
provisioners and its options.
## Initializing PKI and configuring the Certificate Authority
To initialize a PKI and configure the Step Certificate Authority run:
> **NOTE**: `step ca init` only initialize an x509 CA. If you
> would like to initialize an SSH CA as well, add the `--ssh` flag.
```
step ca init [--ssh]
```
You'll be asked for a name for your PKI. This name will appear in your CA
certificates. It doesn't really matter what you choose. The name of your
organization or your project will suffice.
If you run:
```
tree $(step path)
```
You should see:
```
.
├── certs
│   ├── intermediate_ca.crt
│   ├── root_ca.crt
│   ├── ssh_host_key.pub (--ssh only)
│   └── ssh_user_key.pub (--ssh only)
├── config
│   ├── ca.json
│   └── defaults.json
└── secrets
├── intermediate_ca_key
├── root_ca_key
├── ssh_host_key (--ssh only)
└── ssh_user_key (--ssh only)
```
The files created include:
* `root_ca.crt` and `root_ca_key`: the root certificate and private key for
your PKI.
* `intermediate_ca.crt` and `intermediate_ca_key`: the intermediate certificate
and private key that will be used to sign leaf certificates.
* `ssh_host_key.pub` and `ssh_host_key` (`--ssh` only): the SSH host pub/priv key
pair that will be used to sign new host SSH certificates.
* `ssh_user_key.pub` and `ssh_user_key` (`--ssh` only): the SSH user pub/priv key
pair that will be used to sign new user SSH certificates.
* `ca.json`: the configuration file necessary for running the Step CA.
* `defaults.json`: file containing default parameters for the `step` CA cli
interface. You can override these values with the appropriate flags or
environment variables.
All of the files ending in `_key` are password protected using the password
you chose during PKI initialization. We advise you to change these passwords
(using the `step crypto change-pass` utility) if you plan to run your CA in a
non-development environment.
## What's Inside `ca.json`?
`ca.json` is responsible for configuring communication, authorization, and
default new certificate values for the Step CA. Below is a short list of
definitions and descriptions of available configuration attributes.
* `root`: location of the root certificate on the filesystem. The root certificate
is used to mutually authenticate all api clients of the CA.
* `crt`: location of the intermediate certificate on the filesystem. The
intermediate certificate is returned alongside each new certificate,
allowing the client to complete the certificate chain.
* `key`: location of the intermediate private key on the filesystem. The
intermediate key signs all new certificates generated by the CA.
* `password`: optionally store the password for decrypting the intermediate private
key (this should be the same password you chose during PKI initialization). If
the value is not stored in configuration then you will be prompted for it when
starting the CA.
* `address`: e.g. `127.0.0.1:8080` - address and port on which the CA will bind
and respond to requests.
* `dnsNames`: comma separated list of DNS Name(s) for the CA.
* `logger`: the default logging format for the CA is `text`. The other option
is `json`.
* `db`: data persistence layer. See [database documentation](./database.md) for more
info.
* `type`: `badger`, `bbolt`, `mysql`, etc.
* `dataSource`: string that can be interpreted differently depending on the
type of the database. Usually a path to where the data is stored. See the
[database configuration docs](./database.md#configuration) for more info.
* `database`: name of the database. Used for backends that may have multiple
databases. e.g. MySQL
* `valueDir`: directory to store the value log in (Badger specific).
* `crl`: Certificate Revocation List settings:
* `enable`: enables CRL generation (`true` to generate, `false` to disable)
* `generateOnRevoke`: a revoke will generate a new CRL if the crl is enabled.
* `cacheDuration`: the duration until next update of the CRL, defaults to 24h.
* `renewPeriod`: the time between CRL regeneration. If not set ~2/3 of the
cacheDuration will be used.
* `tls`: settings for negotiating communication with the CA; includes acceptable
ciphersuites, min/max TLS version, etc.
* `authority`: controls the request authorization and signature processes.
- `template`: default ASN1DN values for new certificates.
- `claims`: default validation for requested attributes in the certificate request.
Can be overriden by similar claims objects defined by individual provisioners.
* `minTLSCertDuration`: do not allow certificates with a duration less
than this value.
* `maxTLSCertDuration`: do not allow certificates with a duration greater
than this value.
* `defaultTLSCertDuration`: if no certificate validity period is specified,
use this value.
* `disableIssuedAtCheck`: disable a check verifying that provisioning
tokens must be issued after the CA has booted. This is one prevention
against token reuse. The default value is `false`. Do not change this
unless you know what you are doing.
SSH CA properties
* `minUserSSHDuration`: do not allow certificates with a duration less
than this value.
* `maxUserSSHDuration`: do not allow certificates with a duration
greater than this value.
* `defaultUserSSHDuration`: if no certificate validity period is specified,
use this value.
* `minHostSSHDuration`: do not allow certificates with a duration less
than this value.
* `maxHostSSHDuration`: do not allow certificates with a duration
greater than this value.
* `defaultHostSSHDuration`: if no certificate validity period is specified,
use this value.
* `enableSSHCA`: enable all provisioners to generate SSH Certificates.
The deault value is `false`. You can enable this option per provisioner
by setting it to `true` in the provisioner claims.
- `provisioners`: list of provisioners.
See the [provisioners documentation](./provisioners.md). Each provisioner
has an optional `claims` attribute that can override any attribute defined
at the level above in the `authority.claims`.
`step ca init` will generate one provisioner. New provisioners can be added by
running `step ca provisioner add`.
## Running the CA
To start the CA run:
```
export STEPPATH=$(step path)
step-ca $STEPPATH/config/ca.json
```
### Systemctl
Consider adding a service user that will only be used by `systemctl` to manage
the service.
```
$ useradd step
$ passwd -l step
```
Use the following example as a base for your `systemctl` service file:
```
[Unit]
Description=step-ca
After=syslog.target network.target
[Service]
User=step
Group=step
ExecStart=/bin/sh -c '/bin/step-ca /home/step/.step/config/ca.json --password-file=/home/step/.step/pwd >> /var/log/step-ca/output.log 2>&1'
Type=simple
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
```
The following are a few example commands you can use to check the status,
enable on restart, and start your `systemctl` service.
```
# Check the current status of the `step-ca` service
$ systemctl status step-ca
# Configure the `step-ca` process to startup on reboot automatically
$ systemctl enable step-ca
# Start the `step-ca` service.
$ systemctl start step-ca
```
## Configure Your Environment
**Note**: Configuring your environment is only necessary for remote servers
(not the server on which the `step ca init` command was originally run).
Many of the cli utilities under `step ca [sub-command]` interface directly with
a running instance of the Step CA. The CA exposes an HTTP API and clients are
required to connect using HTTP over TLS (aka HTTPS). As part of bootstraping
the Step CA, a certificate was generated using the root of trust that was
created when you initilialized your PKI. In order to properly validate this
certificate clients need access to the public root of trust, aka the public root
certificate. If you are using the `step cli` on the same host where you
initialized your PKI (the `root_ca.crt` is stored on disk locally), then you can
continue to [setting up your environment](#setup-env),
otherwise we will show you how to easily download your root certificate in the
following step.
#### Download the Root Certificate
The next few steps are a guide for downloading the root certificate of your PKI
from a running instance of the CA. First we'll define two servers:
* **remote server**: This is the server where the Step CA is running. This may
also be the server where you initialized your PKI, but for security reasons
you may have done that offline.
* **local server**: This is the server that wants access to the `step ca [sub-command]`
* **ca-url**: This is the url at which the CA is listening for requests. This
should be a combination of the DNS name and port entered during PKI initialization.
In the examples below we will use `https://ca.smallstep.com:8080`.
1. Get the Fingerprint.
From the **remote server**:
```
$ FP=$(step certificate fingerprint $(step path)/certs/root_ca.crt)
```
2. Bootstrap your environment.
From the **local server**:
```
$ step ca bootstrap --fingerprint $FP --ca-url "https://ca.smallstep.com:8080"
$ cat $(step path)/config/defaults.json
```
3. Test.
```
$ step ca health
```
<a name="setup-env"></a>
#### Setting up Environment Defaults
This is optional, but we recommend you populate a `defaults.json` file with a
few variables that will make your command line experience much more pleasant.
You can do this manually or with the step command `step ca bootstrap`:
```
$ step ca bootstrap \
--ca-url https://ca.smallstep.com:8080 \
--fingerprint 0d7d3834cf187726cf331c40a31aa7ef6b29ba4df601416c9788f6ee01058cf3
# Let's see what we got...
$ cat $STEPPATH/config/defaults.json
{
"ca-url": "https://ca.smallstep.com:8080",
"fingerprint": "628cfc85090ca65bb246d224f1217445be155cfc6167db4ed8f1b0e3de1447c5",
"root": "/Users/<you>/src/github.com/smallstep/step/.step/certs/root_ca.crt"
}
# Test it out
$ step ca health
```
* **ca-url** is the DNS name and port that you used when initializing the CA.
* **root** is the path to the root certificate on the file system.
* **fingerprint** is the root certificate fingerprint (SHA256).
You can always override these values with command-line flags or environment
variables.
To manage the CA provisioners you can also add the property **ca-config** with
the path to the CA configuration file, with that property you won't need to add
it in commands like `step ca provisioners [add|remove]`.
**Note**: to manage provisioners you must be on the host on which the CA is
running. You need direct access to the `ca.json` file.
### Hot Reload
It is important that the CA be able to handle configuration changes with no downtime.
Our CA has a built in `reload` function allowing it to:
1. Finish processing existing connections while blocking new ones.
2. Parse the configuration file and re-initialize the API.
3. Begin accepting blocked and new connections.
`reload` is triggered by sending a SIGHUP to the PID (see `man kill`
for your OS) of the Step CA process. A few important details to note when using `reload`:
* The location of the modified configuration must be in the same location as it
was in the original invocation of `step-ca`. So, if the original command was
```
$ step-ca ./.step/config/ca.json
```
then, upon `reload`, the Step CA will read it's new configuration from the same
configuration file.
* Step CA requires the password to decrypt the intermediate certificate, again,
upon `reload`. You can automate this in one of two ways:
* Use the `--password-file` flag in the original invocation.
* Use the top level `password` attribute in the `ca.json` configuration file.
### Let's issue a certificate!
There are two steps to issuing a certificate at the command line:
1. Generate a provisioning token using your provisioning credentials.
2. Generate a CSR and exchange it, along with the provisioning token, for a certificate.
If you would like to generate a certificate from the command line, the Step CLI
provides a single command that will prompt you to select and decrypt an
authorized provisioner and then request a new certificate.
```
$ step ca certificate "foo.example.com" foo.crt foo.key
```
If you would like to generate certificates on demand from an automated
configuration management solution (no user input) you would split the above flow
into two commands.
```
$ TOKEN=$(step ca token foo.example.com \
--kid 4vn46fbZT68Uxfs9LBwHkTvrjEvxQqx-W8nnE-qDjts \
--ca-url https://ca.example.com \
--root /path/to/root_ca.crt --password-file /path/to/provisioner/password)
$ step ca certificate "foo.example.com" foo.crt foo.key --token "$TOKEN"
```
You can take a closer look at the contents of the certificate using `step certificate inspect`:
```
$ step certificate inspect foo.crt
```
### List|Add|Remove Provisioners
The Step CA configuration is initialized with one provisioner; one entity
that is authorized by the CA to generate provisioning tokens for new certificates.
We encourage you to have many provisioners - ideally one for each entity in your
infrastructure.
**Why should I be using multiple provisioners?**
* Each certificate generated by the Step CA contains the ID of the provisioner
that issued the *provisioning token* authorizing the creation of the cert. This
ID is stored in the X.509 ExtraExtensions of the certificate under
`OID: 1.3.6.1.4.1.37476.9000.64.1` and can be inspected by running `step
certificate inspect foo.crt`. These IDs can and should be used to debug and
gather information about the origin of a certificate. If every member of your
ops team and the configuration management tools all use the same provisioner
to authorize new certificates you lose valuable visibility into the workings
of your PKI.
* Each provisioner should require a **unique** password to decrypt it's private key
-- we can generate unique passwords for you but we can't force you to use them.
If you only have one provisioner then every entity in the infrastructure will
need access to that one password. Jim from your dev ops team should not be using
the same provisioner/password combo to authorize certificates for debugging as
Chef is for your CICD - no matter how trustworthy Jim says he is.
Let's begin by listing the existing provisioners:
```
$ bin/step ca provisioner list
```
Now let's add a provisioner for Jim.
```
$ bin/step ca provisioner add jim@smallstep.com --create
```
**NOTE**: This change will not affect the Step CA until a `reload` is forced by
sending a SIGHUP signal to the process.
List the provisioners again and you will see that nothing has changed.
```
$ bin/step ca provisioner list
```
Now let's `reload` the CA. You will need to re-enter your intermediate
password unless it's in your `ca.json` or you are using `--password-file`.
```
$ ps aux | grep step-ca # to get the PID
$ kill -1 <pid>
```
Once the CA is running again, list the provisioners, again.
```
$ bin/step ca provisioner list
```
Boom! Magic.
Now suppose Jim forgets his password ('come on Jim!'), and he'd like to remove
his old provisioner. Get the `kid` (Key ID) of Jim's provisioner by listing
the provisioners and finding the appropriate one. Then run:
```
$ bin/step ca provisioner remove jim@smallstep.com --kid <kid>
```
Then `reload` the CA and verify that Jim's provisioner is no longer returned
in the provisioner list.
We can also remove all of Jim's provisioners, supposing Jim forgot all the passwords
('really Jim?'), by running the following:
```
$ bin/step ca provisioner remove jim@smallstep.com --all
```
The same entity may have multiple provisioners for authorizing different
types of certs. Each of these provisioners must have unique keys.
## Use Custom Claims for Provisioners to Control Certificate Validity etc
It's possible to configure provisioners on the CA to issue certs using
properties specific to their target environments. Most commonly different
validity periods and disabling renewals for certs. Here's how:
```bash
$ step ca init
# complete the init steps
$ step ca provisioner add --create dev@smallstep.com
# lets create a provisioner for dev certs
Please enter a password to encrypt the provisioner private key? password
# add claims inside a provisioner element in ~/.step/config/ca.json
~/.step/config/ca.json
[...]
"authority": {
"provisioners": [
{
"name": "you@smallstep.com",
"type": "jwk",
"key": {
"use": "sig",
"kty": "EC",
"kid": "Kg43gSukHnl8f5NztLPDxqpz_9TNUILnMrIMIa70jOU",
"crv": "P-256",
"alg": "ES256",
"x": "So0JVWFFXo-6GmDwq6WWZZk-AFZt5GKTx5PzdLhdsrQ",
"y": "kVz8pCl2Qx9fZmJZhXGrHpufwNDTp7oHwi8Zaj7rhiQ"
},
"encryptedKey": "...",
+ "claims": {
+ "minTLSCertDuration": "5s",
+ "maxTLSCertDuration": "12h",
+ "defaultTLSCertDuration": "2h",
+ "disableRenewal": true
+ }
}
]
},
[...]
## launch CA...
$ step-ca $(step path)/config/ca.json
Please enter the password to decrypt ~/.step/secrets/intermediate_ca_key: password
2019/02/21 12:09:51 Serving HTTPS on :9443 ...
```
See the [`provisioner doc`][1] for details on all available provisioner claims.
The durations are strings which are a sequence of decimal numbers, each with
optional fraction and a unit suffix, such as "300ms" or "2h45m". Valid time
units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Now certs issued by the `dev@smallstep.com` provisioner will be valid for two
hours and deny renewals. Command line flags allow validity extension up to 12h,
please see [`step ca certificate`][2]'s docs for details.
[1]: ./provisioners.md
[2]: https://smallstep.com/docs/cli/ca/certificate/
```bash
# grab a cert, will also work with 'step ca token' flow
$ step ca certificate localhost site.crt site.key
Use the arrow keys to navigate: ↓ ↑ → ←
What provisioner key do you want to use?
IY7gYg_cDKmXtcs1sbhdBDDb9K9YvLO5aHzArjaayso (sebastian@smallstep.com)
▸ uBYWYDCpeJu_IYzMGPZ1LJJTdlaiJQfdpkOVewbjy-8 (dev@smallstep.com)
✔ Please enter the password to decrypt the provisioner key: password
✔ CA: https://ca.smallstep.com:9443/1.0/sign
✔ Certificate: site.crt
✔ Private Key: site.key
$ step certificate inspect site.crt --format json | jq .validity
{
"start": "2019-02-21T20:19:06Z",
"end": "2019-02-21T22:19:06Z",
"length": 7200
}
# renewals will be denied for certs issued by this provisioner
$ step ca renew site.crt site.key
error renewing certificate: Unauthorized
```
## Use Oauth OIDC to obtain personal certificates
To authenticate users with the CA you can leverage services that expose OAuth
OpenID Connect identity providers. One of the most common providers, and the
one we'll use in this example, is G-Suite.
Navigate to the Google APIs developer console and pick a suitable project from the
top navbar's dropdown.
![Google Dev Console](./images/oidc1.png)
In the masthead navigation click **Credentials** (key symbol) and then "OAuth
consent screen" from the subnav. Fill out naming details, all mandatory fields,
and decide if your app is of type **Public** or **Internal**. Internal
will make sure the access scope is bound to your G-Suite organization.
**Public** will let anybody with a Google Account log in, incl.
`gmail.com` accounts.
Move back to **Credentials** on the subnav and choose "OAuth client ID" from the
**Create credentials** dropdown. Since OIDC will be used from the `step CLI` pick **Other**
from the available options and pick a name (e.g. **Step CLI**).
![Create credential](./images/oidc2.png)
On successful completion, a confirmation modal with both `clientID` and
`clientSecret` will be presented. Please note that the `clientSecret` will
allow applications access to the configured OAuth consent screen. However, it
will not allow direct authentication of users without their own MfA credentials
per account.
![OIDC credentials](./images/oidc3.png)
Now using `clientID` and `clientSecret` run the following command to add
G-Suite as a provisioner to `step certificates`. Please see [`step ca
provisioner add`](https://smallstep.com/docs/cli/ca/provisioner/add/)'s docs
for all available configuration options and descriptions.
```bash
$ step ca provisioner add Google --type oidc --ca-config $(step path)/config/ca.json \
--client-id 972437157139-ssiqna0g4ibuhafl3pkrrcb52tbroekt.apps.googleusercontent.com \
--client-secret RjEk-GwKBvdsFAICiJhn_RiF \
--configuration-endpoint https://accounts.google.com/.well-known/openid-configuration \
--domain yourdomain.com --domain gmail.com
```
Start up the online CA or send a HUP signal if it's already running to reload
the configuration and pick up the new provisioner. Now users should be able to
obtain certificates using the familiar `step ca certificate` flow:
```bash
$ step ca certificate sebastian@smallstep.com personal.crt personal.key
Use the arrow keys to navigate: ↓ ↑ → ←
What provisioner key do you want to use?
fYDoiQdYueq_LAXx2kqA4N_Yjf_eybe-wari7Js5iXI (admin)
▸ 972437157139-ssiqna0g4ibuhafl3pkrrcb52tbroekt.apps.googleusercontent.com (Google)
✔ Key ID: 972437157139-ssiqna0g4ibuhafl3pkrrcb52tbroekt.apps.googleusercontent.com (Google)
✔ CA: https://localhost
✔ Certificate: personal.crt
✔ Private Key: personal.key
$ step certificate inspect --short personal.crt ⏎
X.509v3 TLS Certificate (ECDSA P-256) [Serial: 6169...4235]
Subject: 106202051347258973689
sebastian@smallstep.com
Issuer: Local CA Intermediate CA
Provisioner: Google [ID: 9724....com]
Valid from: 2019-03-26T20:36:28Z
to: 2019-03-27T20:36:28Z
```
Now it's easy for anybody in the G-Suite organization to obtain valid personal
certificates!
## Notes on Securing the Step CA and your PKI.
In this section we recommend a few best practices when it comes to
running, deploying, and managing your own online CA and PKI. Security is a moving
target and we expect out recommendations to change and evolve as well.
### Initializing your PKI
When you initialize your PKI two private keys are generated; one intermediate
private key and one root private key. It is very important that these private keys
are kept secret. The root private key should be moved around as little as possible,
preferably not all - meaning it never leaves the server on which it was created.
### Passwords
When you initialize your PKI (`step ca init`) the root and intermediate
private keys will be encrypted with the same password. We recommend that you
change the password with which the intermediate is encrypted at your earliest
convenience.
```
$ step crypto change-pass $STEPPATH/secrets/intermediate_ca_key
```
Once you've changed the intermediate private key password you should never have
to use the root private key password again.
We encourage users to always use a password manager to generate random passwords
or let Step CLI generate passwords for you.
The next important matter is how your passwords are stored. We recommend using a
[password manager](https://en.wikipedia.org/wiki/List_of_password_managers).
There are many to choose from and the choice will depend on the risk & security
profile of your organization.
In addition to using a password manager to store all passwords (private key,
provisioner, etc.) we recommend using a threshold cryptography algorithm like
[Shamir's Secret Sharing](https://en.wikipedia.org/wiki/Shamir's_Secret_Sharing)
to divide the root private key password across a handful of trusted parties.
### Provisioners
When you initialize your PKI (`step ca init`) a default provisioner will be created
and it's private key will be encrypted using the same password used to encrypt
the root private key. Before deploying the Step CA you should remove this
provisioner and add new ones that are encrypted with new, secure, random passwords.
See the section on [managing provisioners](#listaddremove-provisioners).
### Deploying
* Refrain from entering passwords for private keys or provisioners on the command line.
Use the `--password-file` flag whenever possible.
* Run the Step CA as a new user and make sure that the config files, private keys,
and passwords used by the CA are stored in such a way that only this new user
has permissions to read and write them.
* Use short lived certificates. Our default validity period for new certificates
is 24 hours. You can configure this value in the `ca.json` file. Shorter is
better - less time to form an attack.
* Short lived certificates are **not** a replacement for CRL and OCSP. CRL and OCSP
are features that we plan to implement, but are not yet available. In the mean
time short lived certificates are a decent alternative.
* Keep your hosts secure by enforcing AuthN and AuthZ for every connection. SSH
access is a big one.
<a name="step-ca-ha"></a>
## Notes on Running Step CA as a Highly Available Service
**CAUTION**: `step-ca` is built to scale horizontally. However, the creators
and maintainers do not regularly test in an HA environment using mulitple
instances. You may run into issues we did not plan for. If this happens, please
[open an issue][3].
### Considerations
A few things to consider / implement when running multiple instances of `step-ca`:
* Use `MySQL` DB: The default `Badger` DB cannot be read / written by more than one
process simultaneously. The only supported DB that can support multiple instances
is `MySQL`. See the [database documentation][4] for guidance on configuring `MySQL`.
* The ACME server has known concurrency limitations when using the same account to
manage multiple orders. The recommended temporary workaround is to generate
an ephemeral account keypair for each new ACME order, or to ensure that ACME
orders owned by the same account are managed serially. The issue tracking
this limitation can be found [here](https://github.com/smallstep/certificates/issues/341).
* Synchronize `ca.json` across instances: `step-ca` reads all of it's
configuration (and all of the provisioner configuration) from the `ca.json` file
specified on the command line. If the `ca.json` of one instance is modified
(either manually or using a command like `step ca provisioner (add | remove)`)
the other instances will not pick up on this change until the `ca.json` is
copied over to the correct location for each instance and the instance itself
is `SIGHUP`'ed (or restarted). It's recommended to use a configuration management
(ansible, chef, salt, puppet, etc.) tool to synchronize `ca.json` across instances.
[3]: https://github.com/smallstep/certificates/issues
[4]: ./database.md

View file

@ -1,41 +0,0 @@
# Step Certificates Documentation
## Note: Much of [our documentation has moved](https://smallstep.com/docs)
Index of Documentation and Tutorials for using and deploying the `step certificates`.
[![GitHub release](https://img.shields.io/github/release/smallstep/certificates.svg)](https://github.com/smallstep/certificates/releases)
[![CA Image](https://images.microbadger.com/badges/image/smallstep/step-ca.svg)](https://microbadger.com/images/smallstep/step-ca)
[![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/certificates)](https://goreportcard.com/report/github.com/smallstep/certificates)
[![Build Status](https://travis-ci.com/smallstep/certificates.svg?branch=master)](https://travis-ci.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)
## Table of Contents
* **General Info**
* [Website](https://smallstep.com)
* [Installation Guide](https://smallstep.com/docs/step-ca/installation)
* [Getting Started](https://smallstep.com/docs/step-ca/getting-started): in depth guide on getting started
with `step-ca`, including all configuration options.
* [Contributor's Guide](./CONTRIBUTING.md)
* [Sane Defaults](https://smallstep.com/docs/step-ca/certificate-authority-server-production#sane-cryptographic-defaults): default algorithms and attributes used
in cryptographic primitives and why they were selected.
* [Frequently Asked Questions](./questions.md)
* Check out our [Blog](https://smallstep.com/blog/). We post quality
educational content as well as periodic updates on new releases.
* **API**: Guides to using the API via the `step` CLI.
* [Revoking Certificates](https://smallstep.com/docs/step-ca/revocation)
* [Persistence Layer](https://smallstep.com/docs/step-ca/configuration#databases): description and guide to using `step certificates`'
persistence layer for storing certificate management metadata.
* **Tutorials**: Guides for deploying and getting started with `step` in various environments.
* [Docker](./docker.md)
* [Kubernetes](../autocert/README.md)
## Further Reading
* [Use TLS Everywhere](https://smallstep.com/blog/use-tls.html)
* [Everything you should know about certificates and PKI but are too afraid to ask](https://smallstep.com/blog/everything-pki.html)

View file

@ -1,161 +0,0 @@
# Using ACME with `step-ca `
Lets assume youve [installed
`step-ca`](https://smallstep.com/docs/getting-started/#1-installing-step-and-step-ca)
(e.g., using `brew install step`), have it running at `https://ca.internal`,
and youve [bootstrapped your ACME client
system(s)](https://smallstep.com/docs/getting-started/#bootstrapping) (or at
least [installed your root
certificate](https://smallstep.com/docs/cli/ca/root/) at
`~/.step/certs/root_ca.crt`).
## Enabling ACME
To enable ACME, simply [add an ACME provisioner](https://smallstep.com/docs/cli/ca/provisioner/add/) to your `step-ca` configuration
by running:
```
$ step ca provisioner add my-acme-provisioner --type ACME
```
> NOTE: The above command will add a new provisioner of type `ACME` and name
> `my-acme-provisioner`. The name is used to identify the provisioner
> (e.g. you cannot have two `ACME` provisioners with the same name).
Now restart or SIGHUP `step-ca` to pick up the new configuration.
Thats it.
## Configuring Clients
To configure an ACME client to connect to `step-ca` you need to:
1. Point the client at the right ACME directory URL
2. Tell the client to trust your CAs root certificate
Once certificates are issued, youll also need to ensure theyre renewed before
they expire.
### Pointing Clients at the right ACME Directory URL
Most ACME clients connect to Lets Encrypt by default. To connect to `step-ca`
you need to point the client at the right [ACME directory
URL](https://tools.ietf.org/html/rfc8555#section-7.1.1).
A single instance of `step-ca` can have multiple ACME provisioners, each with
their own ACME directory URL that looks like:
```
https://{ca-host}/acme/{provisioner-name}/directory
```
We just added an ACME provisioner named “acme”. Its ACME directory URL is:
```
https://ca.internal/acme/acme/directory
```
### Telling clients to trust your CAs root certificate
Communication between an ACME client and server [always uses
HTTPS](https://tools.ietf.org/html/rfc8555#section-6.1). By default, clients
will validate the servers HTTPS certificate using the public root certificates
in your systems [default
trust](https://smallstep.com/blog/everything-pki.html#trust-stores) store.
Thats fine when youre connecting to Lets Encrypt: its a public CA and its
root certificate is in your systems default trust store already. Your internal
root certificate isnt, so HTTPS connections from ACME clients to `step-ca` will
fail.
There are two ways to address this problem:
1. Explicitly configure your ACME client to trust `step-ca`'s root certificate, or
2. Add `step-ca`'s root certificate to your systems default trust store (e.g.,
using [`step certificate
install`](https://smallstep.com/docs/cli/certificate/install/))
If youre using your CA for TLS in production, explicitly configuring your ACME
client to only trust your root certificate is a better option. Well
demonstrate this method with several clients below.
If youre simulating Lets Encrypt in pre-production, installing your root
certificate is a more faithful simulation of production. Once your root
certificate is installed, no additional client configuration is necessary.
> Caution: adding a root certificate to your systems trust store is a global
> operation. Certificates issued by your CA will be trusted everywhere,
> including in web browsers.
### Example using [`certbot`](https://certbot.eff.org/)
[`certbot`](https://certbot.eff.org/) is the grandaddy of ACME clients. Built
and supported by [the EFF](https://www.eff.org/), its the standard-bearer for
production-grade command-line ACME.
To get a certificate from `step-ca` using `certbot` you need to:
1. Point `certbot` at your ACME directory URL using the `--`server flag.
2. Tell `certbot` to trust your root certificate using the `REQUESTS_CA_BUNDLE` environment variable.
For example:
```
$ sudo REQUESTS_CA_BUNDLE=$(step path)/certs/root_ca.crt \
certbot certonly -n --standalone -d foo.internal \
--server https://ca.internal/acme/acme/directory
```
`sudo` is required in `certbot`'s [*standalone*
mode](https://certbot.eff.org/docs/using.html#standalone) so it can listen on
port 80 to complete the `http-01` challenge. If you already have a webserver
running you can use [*webroot*
mode](https://certbot.eff.org/docs/using.html#webroot) instead. With the
[appropriate plugin](https://certbot.eff.org/docs/using.html#dns-plugins)
`certbot` also supports the `dns-01` challenge for most popular DNS providers.
Deeper integrations with [nginx](https://certbot.eff.org/docs/using.html#nginx)
and [apache](https://certbot.eff.org/docs/using.html#apache) can even configure
your server to use HTTPS automatically (we'll set this up ourselves later). All
of this works with `step-ca`.
You can renew all of the certificates you've installed using `cerbot` by running:
```
$ sudo REQUESTS_CA_BUNDLE=$(step path)/certs/root_ca.crt certbot renew
```
You can automate renewal with a simple `cron` entry:
```
*/15 * * * * root REQUESTS_CA_BUNDLE=$(step path)/certs/root_ca.crt certbot -q renew
```
The `certbot` packages for some Linux distributions will create a `cron` entry
or [systemd
timer](https://stevenwestmoreland.com/2017/11/renewing-certbot-certificates-using-a-systemd-timer.html)
like this for you. This entry won't work with `step-ca` because it [doesn't set
the `REQUESTS_CA_BUNDLE` environment
variable](https://github.com/certbot/certbot/issues/7170). You'll need to
manually tweak it to do so.
More subtly, `certbot`'s default renewal job is tuned for Let's Encrypt's 90
day certificate lifetimes: it's run every 12 hours, with actual renewals
occurring for certificates within 30 days of expiry. By default, `step-ca`
issues certificates with *much shorter* 24 hour lifetimes. The `cron` entry
above accounts for this by running `certbot renew` every 15 minutes. You'll
also want to configure your domain to only renew certificates when they're
within a few hours of expiry by adding a line like:
```
renew_before_expiry = 8 hours
```
to the top of your renewal configuration (e.g., in `/etc/letsencrypt/renewal/foo.internal.conf`).
## Feedback
`step-ca` should work with any ACMEv2
([RFC8555](https://tools.ietf.org/html/rfc8555)) compliant client that supports
the http-01 or dns-01 challenge.
Post feedback on [our GitHub Discussions tab](https://github.com/smallstep/certificates/discussions),
or [create a bug report issue](https://github.com/smallstep/certificates/issues/new?template=bug_report.md).

View file

@ -1,217 +0,0 @@
# Registration Authorities
This document describes how to use an external registration authority (RA), aka
certificate authority service (CAS) to sign X.509 certificates requests.
A CAS is a system that implements an API to sign certificate requests, the
difference between CAS and KMS is that the latter can sign any data, while CAS
is intended to sign only X.509 certificates.
`step-ca` defines an interface that can be implemented to support other
registration authorities, currently only CloudCAS and the default SoftCAS are
implemented.
The `CertificateAuthorityService` is defined in the package
`github.com/smallstep/certificates/cas/apiv1` and it is:
```go
type CertificateAuthorityService interface {
CreateCertificate(req *CreateCertificateRequest) (*CreateCertificateResponse, error)
RenewCertificate(req *RenewCertificateRequest) (*RenewCertificateResponse, error)
RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error)
}
```
The same package defines another interface that is used to get the root
certificates from the CAS:
```go
type CertificateAuthorityGetter interface {
GetCertificateAuthority(req *GetCertificateAuthorityRequest) (*GetCertificateAuthorityResponse, error)
}
```
## SoftCAS
SoftCAS is the default implementation supported by `step-ca`. No special
configurations are required to enable it.
SoftCAS generally uses certificates and keys in the filesystem, but a KMS can
also be used instead of a key file for signing certificates. See [KMS](kms.md)
for more information.
## CloudCAS
CloudCAS is the implementation of the `CertificateAuthorityService` and
`CertificateAuthorityGetter` interfaces using [Google's Certificate Authority
Service](https://cloud.google.com/certificate-authority-service/).
Before enabling CloudCAS in `step-ca` you do some steps in Google Cloud Console
or using `gcloud` CLI:
1. Create or define a project to use. Let's say the name is `smallstep-cas-test`.
2. Create the KMS keyring and keys for root and intermediate certificates:
```sh
# Create key ring
gcloud kms keyrings create kr1 --location us-west1
# Create key for Root certificate
gcloud kms keys create k1 \
--location us-west1 \
--keyring kr1 \
--purpose asymmetric-signing \
--default-algorithm ec-sign-p256-sha256 \
--protection-level software
# Create key for Intermediate certicate
gcloud kms keys create k2 \
--location us-west1 \
--keyring kr1 \
--purpose asymmetric-signing \
--default-algorithm ec-sign-p256-sha256 \
--protection-level software
# Put the resource name for version 1 of the new KMS keys into a shell variable.
# This will be used in the other instructions below.
KMS_ROOT_KEY_VERSION=$(gcloud kms keys versions describe 1 --key k1 --keyring kr1 --location us-west1 --format "value(name)")
KMS_INTERMEDIATE_KEY_VERSION=$(gcloud kms keys versions describe 1 --key k2 --keyring kr1 --location us-west1 --format "value(name)")
```
3. Enable the CA service API. You can do it on the console or running:
```sh
gcloud services enable privateca.googleapis.com
```
4. Configure IAM. Create a service account using Google Console or running:
```sh
# Create service account
gcloud iam service-accounts create step-ca-sa \
--project smallstep-cas-test \
--description "Step-CA Service Account" \
--display-name "Step-CA Service Account"
# Add permissions to use the privateca API
gcloud projects add-iam-policy-binding smallstep-cas-test \
--member=serviceAccount:step-ca-sa@smallstep-cas-test.iam.gserviceaccount.com \
--role=roles/privateca.caManager
gcloud projects add-iam-policy-binding smallstep-cas-test \
--member=serviceAccount:step-ca-sa@smallstep-cas-test.iam.gserviceaccount.com \
--role=roles/privateca.certificateRequester
# Download the credentials.file
gcloud iam service-accounts keys create credentials.json \
--iam-account step-ca-sa@smallstep-cas-test.iam.gserviceaccount.com
```
5. Create a Root CA. You can do this on the console or running:
```sh
gcloud beta privateca roots create prod-root-ca \
--location us-west1 \
--kms-key-version "$KMS_ROOT_KEY_VERSION" \
--subject "CN=Example Root CA, O=Example LLC" \
--max-chain-length 2
```
6. Create an Intermediate CA. You can do this on the console or running:
```sh
gcloud beta privateca subordinates create prod-intermediate-ca \
--location us-west1 \
--issuer prod-root-ca \
--issuer-location us-west1 \
--kms-key-version "$KMS_INTERMEDIATE_KEY_VERSION" \
--subject "CN=Example Intermediate CA, O=Example LLC" \
--reusable-config "subordinate-server-tls-pathlen-0"
```
Now it's time to enable it in `step-ca` by adding some new files in the
`"authority"` section of the `ca.json`.
```json
{
"authority": {
"type": "cloudCAS",
"credentialsFile": "/path/to/credentials.json",
"certificateAuthority": "projects/<name>/locations/<loc>/certificateAuthorities/<ca-name>",
}
}
```
* **type** defines the name of the CAS to use, _cloudCAS_ must be used to enable it.
* **credentialsFile** defines the path to a Google Cloud credential file with
access to Google's Certificate AuthorityService. We created this file before
in step 4. Instead of setting this property, the environment variable
`GOOGLE_APPLICATION_CREDENTIALS` can be pointed to the file to use. Or if the
`step-ca` is running in Google Cloud, the default service account in the
machine can also be used.
* **certificateAuthority** defines the Google Cloud resource to the intermediate
(or subordinated) certificate to use. We created this resource in step 6.
As we said before, the CloudCAS implementation in `step-ca` also defines the
interface `CertificateAuthorityGetter`, this allows `step-ca` to automatically
download the root certificate from Cloud CAS. In the `ca.json` now you don't
need to configure `"root"`, and because the intermediate is in Google Cloud,
`"crt"` and `"key"` are no needed. A full `ca.json` can look like:
```json
{
"address": ":443",
"dnsNames": ["ca.example.com"],
"logger": {"format": "text"},
"db": {
"type": "badger",
"dataSource": "/home/jane/.step/db",
},
"authority": {
"type": "cloudCAS",
"credentialsFile": "/home/jane/.step/credentials.json",
"certificateAuthority": "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/prod-intermediate-ca",
"provisioners": [
{
"type": "JWK",
"name": "jane@example.com",
"key": {
"use": "sig",
"kty": "EC",
"kid": "ehFT9BkVOY5k_eIiMax0ZxVZCe2hlDVkMwZ2Y78av4s",
"crv": "P-256",
"alg": "ES256",
"x": "GtEftN0_ED1lNc2SEUJDXV9EMi7JY-kqINPIEQJIkjM",
"y": "8HYFdNe1MbWcbclF-hU1L80SCmMcZQI6vZfTOXfPOjg"
},
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiSjBSWnY5UFZrM3JKRUJkem5RbExzZyJ9.Fiwvo-RIKU5G6v5udeCT1nlX87ElxrocP2FcgNs3AqEz5OH9H4suew.NmzUJR_9xv8ynQC8.dqOveA_G5kn5lxjxnEZoJCystnJMVYLkZ_8CVzfJQhYchbZfNk_-FKdIuQxeWWBzvmomsILFNtLOIUoqSt30qk83lFyGQWN8Ke2bK5DhuwojF7RI_UqkMyiKP0F28Z4ZFhfQP5D2ZT_stoFaMlU8eak0-T8MOiBIfdAJTWM9x2DN-68mtUBuL5z5eU8bqsxELnjGauD_GHTdnduOosmYsw8vp_PmffTTwqUzDFH1RhkeSmRFRZntAizZMGYkxLamquHI3Jvuqiv4eeJ3yLqh3Ppyo_mVQKnxM7P9TyTxcvLkb2dB3K-cItl1fpsz92cy8euKsKG8n5-hKFRyPfY.j7jBN7nUwatoSsIZuNIwHA"
}
]
},
"tls": {
"cipherSuites": [
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
],
"minVersion": 1.2,
"maxVersion": 1.3,
"renegotiation": false
}
}
```
The we only need to run `step-ca` as usual, but this time, the CA will print the
root fingerprint too:
```sh
$ step-ca /home/jane/.step/config/ca.json
2020/09/22 13:17:15 Using root fingerprint '3ef16343cf0952eedbe2b843066bb798fa7a7bceb16aa285e8b0399f661b28b7'
2020/09/22 13:17:15 Serving HTTPS on :9000 ...
```
We will need to bootstrap once our environment using the printed fingerprint:
```sh
step ca bootstrap --ca-url https://ca.example.com --fingerprint 3ef16343cf0952eedbe2b843066bb798fa7a7bceb16aa285e8b0399f661b28b7
```
And now we can sign sign a certificate as always:
```sh
step ca certificate test.example.com test.crt test.key
```

View file

@ -1,106 +0,0 @@
# Step Certificates Database
`step certificates` uses a simple key-value interface over popular database
implementations to store persistent certificate management meta-data.
Our recommended default database implementation is
[nosql-Badger](https://github.com/smallstep/nosql/badger) - a NoSQL interface
over the popular [Badger](https://github.com/dgraph-io/badger) database.
## What will the database store?
As a first pass, the database layer will store every certificate (along with
metadata surrounding the provisioning of the certificate) and revocation data
that will be used to enforce passive revocation.
## Implementations
Current implementations include Badger (default), BoltDB, and MysQL.
- [ ] Memory
- [x] No database
- [x] [BoltDB](https://github.com/etcd-io/bbolt) -- etcd fork.
- [x] [Badger](https://github.com/dgraph-io/badger)
- [x] [MySQL/MariaDB](https://github.com/go-sql-driver/mysql)
- [ ] PostgreSQL
- [ ] Cassandra
Let us know which integration you would like to see next by opening an issue or PR.
## Configuration
Configuring `step certificates` to use a database is as simple as adding a
top-level `db` stanza to `$(step path)/config/ca.json`. Below are a few examples for supported databases:
### Badger
```
{
...
"db": {
"type": "badger",
"dataSource": "./.step/db",
"valueDir": "./.step/valuedb"
"badgerFileLoadingMode": "MemoryMap"
},
...
}
```
#### Options for `db`:
* `type`
* `badger` - currently refers to Badger V1. However, as Badger V1 is deprecated,
this will refer to Badger V2 starting with a the next major version release.
* `badgerV1` - explicitly select Badger V1.
* `badgerV2` - explicitly select Badger V2. Anyone looking to use Badger V2
will need to set it explicitly until it becomes the default.
* `dataSource` - path, database directory.
* `valueDir` [optional] - path, value directory, only if different from `dataSource`.
* `badgerFileLoadingMode` [optional] - can be set to `FileIO` (instead of the default
`MemoryMap`) to avoid memory-mapping log files. This can be
useful in environments with low RAM. Make sure to use `badgerV2` as the
database `type` if using this option.
* `MemoryMap` - default.
* `FileIO` - This can be useful in environments with low RAM.
### BoltDB
```
{
...
"db": {
"type": "bbolt",
"dataSource": "./stepdb"
},
...
},
```
### MySQL
```
{
...
"db": {
"type": "mysql",
"dataSource": "user:password@tcp(127.0.0.1:3306)/",
"database": "myDatabaseName"
},
...
},
```
## Schema
As the interface is a key-value store, the schema is very simple. We support
`tables`, `keys`, and `values`. An entry in the database is a `[]byte value`
that is indexed by `[]byte table` and `[]byte key`.
## Data Backup
Backing up your data is important, and it's good hygiene. We chose
[Badger](https://github.com/dgraph-io/badger) as our default file based data
storage backend because it has mature tooling for running common database
tasks. See the [documentation](https://github.com/dgraph-io/badger#database-backup)
for a guide on backing up your data.

View file

@ -1,227 +0,0 @@
# Default Algorithms and Attributes for Tokens, Keys, Certificates, etc.
The `step` ecosystem aims to be a "easy to use and hard to misuse" suite of PKI
tools. This means we need to select sane defaults for the myriad
configuration options that exist when using cryptographic primitives and higher
order abstractions (e.g. JWTs).
Below we document significant configuration options that we have selected as
defaults. These selections will change and evolve over time; security and
cryptography are constantly changing in response to real world pressures. We
intend for this document be an accurate representation of current best practices
in the industry, and to have these practices codified as defaults in the `step
certificates` code base. If you have questions, suggestions, or comments about
any of these decisions please let us know by opening an issue on this repo,
reaching out through [GitHub Discussions](https://github.com/smallstep/certificates/discussions).
## Tokens
We use JWTs (JSON Web Tokens) to prove authenticity and identity within the
Step ecosystem. JWTs have received negative attention because they are easy to
misuse and misconfigure. We agree! But lots of things are easy to misuse. We also
believe that when configured well JWTs are a great way to sign and encode data.
Our JWT's are, by default, short-lived (5 minute lifespan) and one time use
during the lifetime of the Step CA. We use a 1 minute clock drift leeway
because that was the recommended default in the reputable JWT package that we
chose. If using Step JWTs or your own JWTs in your code be sure to verify and
validate every single standard attribute of the JWT. JWTs, like all
cryptographic tools, are useless without proper attention to configuration and
guidelines.
## Keys
RSA keys don't scale very well. To get 128 bits of security, you need 3,072-bit
RSA keys, which are noticeably slower. ECDSA keys provide an alternative
that offers better security and better performance. At 256 bits, ECDSA keys
provide 128 bits of security. A small number of older clients don't support
ECDSA, but most modern clients do.
**Default Key Type**: ECDSA
**Default Curve Bits**: P-256
We've chosen the AES encryption algorithm (aka Rijndael) for writing private
keys to disk because it was the official choice of the Advanced
Encryption Standard contest. The three supported key sizes are 128, 192, and
256. Each of these is considered to be unbreakable for the forseeable future,
therefore we chose 128 bits as our default because the performance is
better (as compared to the greater key sizes) and because we agree, with
the designers of the algorithm, that 128 bits are quite sufficient for
most security needs.
**Default PEMCipher**: AES128
## X.509 Certificate Defaults
### Root Certificate
* Validity (10 year window)
* **Not Before**: Now
* **Not After**: Now + 10 years
A 10 year window seems advisable until software and tools can be written
for rotating the root certificate.
* **Basic Constraints**
* **CA**: TRUE
The root certificate is a Certificate Authority, it will be used to sign
other Certificates.
* **pathlen**: 1
The path length constraint expresses the number of possible intermediate
CA certificates in a path built from an end-entity certificate up to the
CA certificate. An absent path length constraint means that there is no
limitation to the number of intermediate certificates from end-entity to
the CA certificate. The smallstep PKI has only one intermediate CA
certificate between end-entity certificates and the root CA certificcate.
* **Key Usage** describes how the certificate can be used.
* **Certificate Sign**
Indicates that our root public key will be used to verify a signature on
certificates.
* **CRL Sign**
Indicates that our root public key will be used to verify a signature on
revocation information, such as CRL.
### Intermediate Certificate
* Validity (10 year window)
* **Not Before**: Now
* **Not After**: Now + 10 years
A 10 year window seems advisable until software and tools can be written
for rotating the root certificate.
* **Basic Constraints**
* **CA**: TRUE
The intermediate certificate is a Certificate Authority, used to sign
end-entity (service, process, job, etc.) certificates.
* **pathlen**: 0
The path length constraint expresses the number of possible intermediate
CA certificates in a path built from an end-entity certificate up to the
CA certificate. An absent path length constraint means that there is no
limitation to the number of intermediate certificates from end-entity to
the CA certificate. There are no additional intermediary certificates in
the path between the smallstep intermediate CA and end-entity certificates.
* **Key Usage**
* **Certificate Signing**
Indicates that our the intermediate private key can be used to sign
certificate requests.
* **CRL Sign**
Indicates that this public key can be used to verify a signature on
revocation information, such as CRL.
### Leaf Certificate - End Entity Certificate (certificates returned by the CA)
* Validity (24 hour window)
* **Not Before**: Now
* **Not After**: Now + 24 hours
The default is a 24hr window. This value is somewhat arbitrary, however,
our goal is to have seamless end-entity certificate rotation (we are
getting close). Rotating certificates frequently is good security hygiene
because it gives bad actors very little time to form an attack and limits
the usefulness of any single private key in the system. We will continue
to work towards decreasing this window because we believe it significantly
reduces probability and effectiveness of any attack.
* **Key Usage**
* **Key Encipherment**
Indicates that a certificate will be used with a protocol that encrypts keys.
* **Digital Signature**
Indicates that this public key may be used as a digital signature to
support security services that enable entity authentication and data
origin authentication with integrity.
* **Extended Key Usage**
* **TLS Web Server Authentication**
Certificate can be used as the server side certificate in the TLS protocol.
* **TLS Web Client Authentication**
Certificate can be used as the client side certificate in the TLS protocol.
## Default TLS Configuration Options
* **Min TLS Version**: TLS 1.2
* **Max TLS Version**: TLS 1.2
The PCI Security Standards Council required all payment processors
and merchants to move to TLS 1.2 and above by June 30, 2018. By setting
TLS 1.2 as the default for all tls protocol negotiation we encourage our
users to adopt the same security conventions.
* **Default Cipher Suites**:
```
[
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
]
```
The default 'ciphersuites' are a list of two cipher combinations. For
communication between services running step there is no need for cipher suite
negotiation. The server can specify a single cipher suite which the client is
already known to support.
Reasons for selecting `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`:
* ECDHE key exchange algorithm has perfect forward secrecy
* ECDSA has smaller keys and better performance (than RSA)
* CHACHA20 with POLY1305 is the cipher mode used by google.
* CHACHA20's performance is better than GCM and CBC.
The http2 spec requires the `TLS_ECDHE_(RSA|ECDSA)_WITH_AES_128_GCM_SHA256`
ciphersuite be accepted by the server, therefore it makes our list of
default ciphersuites until we build the functionality to modify our defaults
based on http version.
* **Approved Cipher Suites**:
```
[
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
]
```
Above is a list of step approved cipher suites. Not all communication
can be mediated with step TLS functionality. For those connections the list of
server supported cipher suites must have more options - in case older clients
do not support our favored cipher suite.
Reasons for selecting these cipher suites can be found in the following
[ssllabs article](https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#23-use-secure-cipher-suites).
* **Renegotation**: Never
TLS renegotiation significantly complicates the state machine and has been
the source of numerous, subtle security issues. Therefore, by default we
disable it.

View file

@ -1,186 +0,0 @@
# Getting started with Docker
## NOTE: This guide is deprecated. Please see [smallstep/step-ca](https://hub.docker.com/r/smallstep/step-ca) on Docker Hub for instructions.
This guide shows how to set up [step certificates](https://github.com/smallstep/certificates) using docker.
For short, we will use **step-ca** to refer to [step certificates](https://github.com/smallstep/certificates).
## Requirements
1. To follow this guide you will need to [install step
cli](https://github.com/smallstep/cli#installation-guide).
2. Get the docker image.
Get the latest version of **step-ca** from the [step-ca docker
hub](https://hub.docker.com/r/smallstep/step-ca):
```sh
$ docker pull smallstep/step-ca
```
3. Create the required volumes.
We need to create a volume in docker where we will store our PKI as well as
the step-ca configuration file.
```sh
$ docker volume create step
```
4. Initialize the PKI.
The simple way to do this is to run an interactive terminal:
```sh
$ docker run -it -v step:/home/step smallstep/step-ca sh
~ $ step ca init
✔ What would you like to name your new PKI? (e.g. Smallstep): Smallstep
✔ What DNS names or IP addresses would you like to add to your new CA? (e.g. ca.smallstep.com[,1.1.1.1,etc.]): localhost
✔ What address will your new CA listen at? (e.g. :443): :9000
✔ What would you like to name the first provisioner for your new CA? (e.g. you@smallstep.com): admin
✔ What do you want your password to be? [leave empty and we'll generate one]: <your password here>
Generating root certificate...
all done!
Generating intermediate certificate...
all done!
✔ Root certificate: /home/step/certs/root_ca.crt
✔ Root private key: /home/step/secrets/root_ca_key
✔ Root fingerprint: f9e45ae9ec5d42d702ce39fd9f3125372ce54d0b29a5ff3016b31d9b887a61a4
✔ Intermediate certificate: /home/step/certs/intermediate_ca.crt
✔ Intermediate private key: /home/step/secrets/intermediate_ca_key
✔ Default configuration: /home/step/config/defaults.json
✔ Certificate Authority configuration: /home/step/config/ca.json
Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.
```
5. Place the PKI root password in a known location.
Our image is expecting the password to be placed in `/home/step/secrets/password`
you can simply go in to the terminal again and write that file:
```sh
$ docker run -it -v step:/home/step smallstep/step-ca sh
~ $ echo <your password here> > /home/step/secrets/password
```
At this time everything is ready to run step-ca!
## Running step certificates
Now that we have configured our environment we are ready to run step-ca.
Expose the server address locally and run the step-ca with:
```sh
$ docker run -d -p 127.0.0.1:9000:9000 -v step:/home/step smallstep/step-ca
```
Let's verify that the service is running with curl:
```sh
$ curl https://localhost:9000/health
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html
curl performs SSL certificate verification by default, using a "bundle"
of Certificate Authority (CA) public keys (CA certs). If the default
bundle file isn't adequate, you can specify an alternate file
using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
the bundle, the certificate verification probably failed due to a
problem with the certificate (it might be expired, or the name might
not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
the -k (or --insecure) option.
HTTPS-proxy has similar options --proxy-cacert and --proxy-insecure.
```
It's working but curl complains because the certificate is not signed by an
accepted certificate authority.
### Notes for running on a Raspberry Pi
When you run step-ca on a Raspberry Pi, you might get the following error in
your continaer logs:
```sh
step-ca | badger 2021/05/08 20:13:12 INFO: All 0 tables opened in 0s
step-ca | Error opening database of Type badger with source /home/step/db: error opening Badger database: Mmap value log file. Path=/home/step/db/000000.vlog. Error=cannot allocate memory
```
To fix it, adjust the `db` configuration in the file `config/ca.json`.
Change the value of `badgerFileLoadingMode` from `""` to `"FileIO"`.
```sh
docker run -it -v step:/home/step smallstep/step-ca sh
~ $ vi config/ca.json
```
You will end up with this:
```json
"db": {
"type": "badger",
"dataSource": "/root/.step/db",
"badgerFileLoadingMode": "FileIO"
},
```
## Dev environment bootstrap
To initialize the development environment we need to grab the Root fingerprint
from the [Initializing the PKI](#initializing-the-pki) step earlier. In the
case of this example:
`f9e45ae9ec5d42d702ce39fd9f3125372ce54d0b29a5ff3016b31d9b887a61a4`. With the
fingerprint we can bootstrap our dev environment.
```sh
$ step ca bootstrap --ca-url https://localhost:9000 --fingerprint f9e45ae9ec5d42d702ce39fd9f3125372ce54d0b29a5ff3016b31d9b887a61a4 --install
The root certificate has been saved in ~/.step/certs/root_ca.crt.
Your configuration has been saved in ~/.step/config/defaults.json.
Installing the root certificate in the system truststore... done.
```
Now [step cli](https://github.com/smallstep/cli) is configured to use step-ca
and our new root certificate is trusted by our local environment.
```sh
$ curl https://localhost:9000/health
{"status":"ok"}
```
And we are able to run web services configured with TLS (and mTLS):
```sh
~ $ step ca certificate localhost localhost.crt localhost.key
✔ Key ID: aTPGWP0qbuQdflR5VxtNouDIOXyNMH1H9KAZKP-UcHo (admin)
✔ Please enter the password to decrypt the provisioner key:
✔ CA: https://localhost:9000/1.0/sign
✔ Certificate: localhost.crt
✔ Private Key: localhost.key
~ $ step ca root root_ca.crt
The root certificate has been saved in root_ca.crt.
~ $ python <<EOF
import BaseHTTPServer, ssl
class H(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200); self.send_header('content-type', 'text/html; charset=utf-8'); self.end_headers()
self.wfile.write(b'\n\xf0\x9f\x91\x8b Hello! Welcome to TLS \xf0\x9f\x94\x92\xe2\x9c\x85\n\n')
httpd = BaseHTTPServer.HTTPServer(('', 8443), H)
httpd.socket = ssl.wrap_socket (httpd.socket, server_side=True, keyfile="localhost.key", certfile="localhost.crt", ca_certs="root_ca.crt")
httpd.serve_forever()
EOF
```
Test from another terminal:
```sh
$ curl https://localhost:8443
👋 Hello! Welcome to TLS 🔒✅
```
Or visit `https://localhost:8443` from your browser.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 MiB

View file

@ -1,237 +0,0 @@
# Key Management Services
This document describes how to use a key management service or KMS to store the
private keys and sign certificates.
Support for multiple KMS are planned, but currently the only Google's Cloud KMS,
and Amazon's AWS KMS are supported. A still experimental version for YubiKeys is
also available if you compile [step-ca](https://github.com/smallstep/certificates)
yourself.
## Google's Cloud KMS
[Cloud KMS](https://cloud.google.com/kms) is the Google's cloud-hosted KMS that
allows you to store the cryptographic keys, and sign certificates using their
infrastructure. Cloud KMS supports two different protection levels, SOFTWARE and
HSM.
To configure Cloud KMS in your CA you need add the `"kms"` property to you
`ca.json`, and replace the property`"key"` with the Cloud KMS key name of your
intermediate key:
```json
{
...
"key": "projects/<project-id>/locations/global/keyRings/<ring-id>/cryptoKeys/<key-id>/cryptoKeyVersions/<version-number>",
...
"kms": {
"type": "cloudkms",
"credentialsFile": "path/to/credentials.json"
}
}
```
In a similar way, for SSH certificate, the SSH keys must be Cloud KMS names:
```json
{
...
"ssh": {
"hostKey": "projects/<project-id>/locations/global/keyRings/<ring-id>/cryptoKeys/<key-id>/cryptoKeyVersions/<version-number>",
"userKey": "projects/<project-id>/locations/global/keyRings/<ring-id>/cryptoKeys/<key-id>/cryptoKeyVersions/<version-number>"
},
}
```
Currently [step](https://github.com/smallstep/cli) does not provide an automatic
way to initialize the public key infrastructure (PKI) using Cloud KMS, but an
experimental tool named `step-cloudkms-init` is available for this use case. At
some point this tool will be integrated into `step` and it will be deleted.
To use `step-cloudkms-init` just enable Cloud KMS in your project and run:
```sh
$ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json
$ step-cloudkms-init --project your-project-id --ssh
Creating PKI ...
✔ Root Key: projects/your-project-id/locations/global/keyRings/pki/cryptoKeys/root/cryptoKeyVersions/1
✔ Root Certificate: root_ca.crt
✔ Intermediate Key: projects/your-project-id/locations/global/keyRings/pki/cryptoKeys/intermediate/cryptoKeyVersions/1
✔ Intermediate Certificate: intermediate_ca.crt
Creating SSH Keys ...
✔ SSH User Public Key: ssh_user_ca_key.pub
✔ SSH User Private Key: projects/your-project-id/locations/global/keyRings/pki/cryptoKeys/ssh-user-key/cryptoKeyVersions/1
✔ SSH Host Public Key: ssh_host_ca_key.pub
✔ SSH Host Private Key: projects/your-project-id/locations/global/keyRings/pki/cryptoKeys/ssh-host-key/cryptoKeyVersions/1
```
See `step-cloudkms-init --help` for more options.
## AWS KMS
[AWS KMS](https://docs.aws.amazon.com/kms/index.html) is the Amazon's managed
encryption and key management service. It creates and store the cryptographic
keys, and use their infrastructure for signing operations. Amazon KMS operations
are always backed by hardware security modules (HSMs).
To configure AWS KMS in your CA you need add the `"kms"` property to you
`ca.json`, and replace the property`"key"` with the AWS KMS key name of your
intermediate key:
```json
{
...
"key": "awskms:key-id=f879f239-feb6-4596-9ed2-b1606277c7fe",
...
"kms": {
"type": "awskms",
"region": "us-east-1"
}
}
```
By default it uses the credentials in `~/.aws/credentials`, but this can be
overridden using the `credentialsFile` option, `region` and `profile` can also
be configured as options. These can also be configured using environment
variables as described by their [session
docs](https://docs.aws.amazon.com/sdk-for-go/api/aws/session/).
To configure SSH certificate signing we do something similar, and replace the
ssh keys with the ones in the KMS:
```json
{
...
"ssh": {
"hostKey": "awskms:key-id=d48e502a-09bc-4bf7-9af8-ae1bccedc931",
"userKey": "awskms:key-id=cf28e942-1e10-4a08-b84c-5359af1b5f12"
},
}
```
The keys can also be just the Amazon's Key ID or the ARN, but using the format
based on the [RFC7512](https://tools.ietf.org/html/rfc7512) will allow more
flexibility for future releases of `step`.
Currently [step](https://github.com/smallstep/cli) does not provide an automatic
way to initialize the public key infrastructure (PKI) using AWS KMS, but an
experimental tool named `step-awskms-init` is available for this use case. At
some point this tool will be integrated into `step` and it will be deleted.
To use `step-awskms-init` make sure to have to have your [environment
configured](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
running `aws configure` and then just run:
```sh
$ bin/step-awskms-init --ssh --region us-east-1
Creating PKI ...
✔ Root Key: awskms:key-id=f53fb767-4029-40ff-b650-0dd35fb661df
✔ Root Certificate: root_ca.crt
✔ Intermediate Key: awskms:key-id=f879f239-feb6-4596-9ed2-b1606277c7fe
✔ Intermediate Certificate: intermediate_ca.crt
Creating SSH Keys ...
✔ SSH User Public Key: ssh_user_ca_key.pub
✔ SSH User Private Key: awskms:key-id=cf28e942-1e10-4a08-b84c-5359af1b5f12
✔ SSH Host Public Key: ssh_host_ca_key.pub
✔ SSH Host Private Key: awskms:key-id=cf28e942-1e10-4a08-b84c-5359af1b5f12
```
The `--region` parameter is only required if your aws configuration does not
define a region. See `step-awskms-init --help` for more options.
## YubiKey
And incomplete and experimental support for [YubiKeys](https://www.yubico.com)
is also available. Support for YubiKeys is not enabled by default and only TLS
signing can be configured.
The YubiKey implementation requires cgo, and our build system does not produce
binaries with it. To enable YubiKey download the source code and run:
```sh
make build GOFLAGS=""
```
The implementation uses [piv-go](https://github.com/go-piv/piv-go), and it
requires PCSC support, this is available by default on macOS and Windows
operating systems, but on Linux piv-go requires PCSC lite.
To install on Debian-based distributions, run:
```sh
sudo apt-get install libpcsclite-dev
```
On Fedora:
```sh
sudo yum install pcsc-lite-devel
```
On CentOS:
```sh
sudo yum install 'dnf-command(config-manager)'
sudo yum config-manager --set-enabled PowerTools
sudo yum install pcsc-lite-devel
```
The initialization of the public key infrastructure (PKI) for YubiKeys, is not
currently integrated into [step](https://github.com/smallstep/cli), but an
experimental tool named `step-yubikey-init` is available for this use case. At
some point this tool will be integrated into `step` and it will be deleted.
To configure your YubiKey just run:
```sh
$ bin/step-yubikey-init
What is the YubiKey PIN?:
Creating PKI ...
✔ Root Key: yubikey:slot-id=9a
✔ Root Certificate: root_ca.crt
✔ Intermediate Key: yubikey:slot-id=9c
✔ Intermediate Certificate: intermediate_ca.crt
```
See `step-yubikey-init --help` for more options.
Finally to enable it in the ca.json, point the `root` and `crt` to the generated
certificates, set the `key` with the yubikey URI generated in the previous step
and configure the `kms` property with the `type` and your `pin` in it.
```json
{
"root": "/path/to/root_ca.crt",
"crt": "/path/to/intermediate_ca.crt",
"key": "yubikey:slot-id=9c",
"kms": {
"type": "yubikey",
"pin": "123456"
},
...
}
```
## SSHAgentKMS
SSHAgentKMS is a KMS that wrapps a ssh-agent which has access to the keys to
sign ssh certificates. This was primarly written to be able to use gpg-agent
to provide the keys stored in a YubiKeys openpgp interface.
```json
{
"kms": {
"type": "sshagentkms"
},
"ssh": {
"hostKey": "sshagentkms:cardno:000123456789",
"userKey": "sshagentkms:cardno:000123456789",
},
...
}
```
This KMS requires that "root", "crt" and "key" are stored in plain files as for
SoftKMS.

View file

@ -1,586 +0,0 @@
# Provisioners
> Note: The canonical documentation for `step-ca` provisioners now lives at
> https://smallstep.com/docs/step-ca/provisioners. Documentation
> found on this page may be out of date.
Provisioners are people or code that are registered with the CA and authorized
to issue "provisioning tokens". Provisioning tokens are single-use tokens that
can be used to authenticate with the CA and get a certificate.
See `step ca provisioner add --help` for documentation and examples on adding
provisioners.
> Attn: We strongly recommend using the `step ca provisioner add ...`
> utility to generate provisioners in your `ca.json` configuration. We often
> encode fields differently in the JSON than you might expect. And you can
> always come in and modify the configuration manually after using the utility.
## Claims
Each provisioner can define an optional `claims` attribute. The settings in this
attribute override any settings in the global `claims` attribute in the authority
configuration.
Example `claims`:
```
...
"claims": {
"minTLSCertDuration": "5m",
"maxTLSCertDuration": "24h",
"defaultTLSCertDuration": "24h",
"disableRenewal": false,
"minHostSSHCertDuration": "5m",
"maxHostSSHCertDuration": "1680h",
"defaultHostSSHCertDuration": "720h",
"minUserSSHCertDuration": "5m",
"maxUserSSHCertDuration": "24h",
"defaultUserSSHCertDuration": "16h",
"enableSSHCA": true
},
...
```
* `claims` (optional): overwrites the default claims set in the authority.
You can set one or more of the following claims:
* `minTLSCertDuration`: do not allow certificates with a duration less than
this value.
* `maxTLSCertDuration`: do not allow certificates with a duration greater than
this value.
* `defaultTLSCertDuration`: if no certificate validity period is specified,
use this value.
* `disableIssuedAtCheck`: disable a check verifying that provisioning tokens
must be issued after the CA has booted. This claim is one prevention against
token reuse. The default value is `false`. Do not change this unless you
know what you are doing.
SSH CA properties
* `minUserSSHCertDuration`: do not allow certificates with a duration less
than this value.
* `maxUserSSHCertDuration`: do not allow certificates with a duration
greater than this value.
* `defaultUserSSHCertDuration`: if no certificate validity period is specified,
use this value.
* `minHostSSHCertDuration`: do not allow certificates with a duration less
than this value.
* `maxHostSSHCertDuration`: do not allow certificates with a duration
greater than this value.
* `defaultHostSSHCertDuration`: if no certificate validity period is specified,
use this value.
* `enableSSHCA`: enable all provisioners to generate SSH Certificates.
The default value is `false`. You can enable this option per provisioner
by setting it to `true` in the provisioner claims.
## Provisioner Types
Each provisioner has a different method of authentication with the CA.
- A JWK provisioner uses a JWT signed by a JWK.
- An OIDC provisioner uses a OIDC token signed by an Identity Provider e.g. Google, Okta, Azure.
- An AWS provisioner uses an Instance Identity Document signed by AWS.
- etc.
### Capabilities by Type
Provisioners are used to authenticate certificate signing requests, and every
provisioner has a slightly different scope of authorization. Below is a table
detailing the authorization capabilities of each provisioner.
Provisioner Capabilities| x509-sign | x509-renew | x509-revoke | ssh-user-cert-sign | ssh-host-cert-sign | ssh-user-cert-renew | ssh-host-cert-renew | ssh-revoke | ssh-rekey
----------- | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-:
JWK | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | 𝗫 | 𝗫 | ✔️ | 𝗫
OIDC | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ <sup id="a1">[1](#f1)</sup> | 𝗫 | 𝗫 | ✔️ | 𝗫
X5C | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | 𝗫 | 𝗫 | 𝗫 | 𝗫
K8sSA | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | 𝗫 | 𝗫 | 𝗫 | 𝗫
ACME | ✔️ | ✔️ | 𝗫 | 𝗫 | 𝗫 | 𝗫 | 𝗫 | 𝗫 | 𝗫
SSHPOP | 𝗫 | 𝗫 | 𝗫 | 𝗫 | 𝗫 | 𝗫 | ✔️ | ✔️ | ✔️
AWS | ✔️ | ✔️ | 𝗫 | 𝗫 | ✔️ | 𝗫 | 𝗫 | 𝗫 | 𝗫
Azure | ✔️ | ✔️ | 𝗫 | 𝗫 | ✔️ | 𝗫 | 𝗫 | 𝗫 | 𝗫
GCP | ✔️ | ✔️ | 𝗫 | 𝗫 | ✔️ | 𝗫 | 𝗫 | 𝗫 | 𝗫
<b id="f1">1</b> Admin OIDC users can generate Host SSH Certificates. Admins can be configured in the OIDC provisioner. [](#a1)
### JWK
JWK is the default provisioner type. It uses public-key cryptography to sign and
validate a JSON Web Token (JWT).
The [step](https://github.com/smallstep/cli) CLI tool will create a JWK
provisioner when `step ca init` is used, and it also contains commands to add
(`step ca provisioner add`) and remove (`step ca provisioner remove`) JWK
provisioners.
In the ca.json configuration file, a complete JWK provisioner example looks like:
```json
{
"type": "JWK",
"name": "you@smallstep.com",
"key": {
"use": "sig",
"kty": "EC",
"kid": "NPM_9Gz_omTqchS6Xx9Yfvs-EuxkYo6VAk4sL7gyyM4",
"crv": "P-256",
"alg": "ES256",
"x": "bBI5AkO9lwvDuWGfOr0F6ttXC-ZRzJo8kKn5wTzRJXI",
"y": "rcfaqE-EEZgs34Q9SSH3f9Ua5a8dKopXNfEzDD8KRlU"
},
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiTlV6MjlEb3hKMVdOaFI3dUNjaGdYZyJ9.YN7xhz6RAbz_9bcuXoymBOj8bOg23ETAdmSCRyHpxGekkV0q3STYYg.vo1oBnZsZjgRu5Ln.Xop8AvZ74h_im2jxeaq-hYYWnaK_eF7MGr4xcZGodMUxp-hGPqS85oWkyprkQLYt1-jXTURfpejtmPeB4-sxgj7OFxMYYus84BdkG9BZgSBmMN9SqZItOv4pqg_NwQA0bv9g9A_e-N6QUFanxuYQsEPX_-IwWBDbNKyN9bXbpEQa0FKNVsTvFahGzOxQngXipi265VADkh8MJLjYerplKIbNeOJJbLd9CbS9fceLvQUNr3ACGgAejSaWmeNUVqbho1lY4882iS8QVx1VzjluTXlAMdSUUDHArHEihz008kCyF0YfvNdGebyEDLvTmF6KkhqMpsWn3zASYBidc9k._ch9BtvRRhcLD838itIQlw",
"claims": {
"minTLSCertDuration": "5m",
"maxTLSCertDuration": "24h",
"defaultTLSCertDuration": "24h",
"disableRenewal": false,
"minHostSSHCertDuration": "5m",
"maxHostSSHCertDuration": "1680h",
"minUserSSHCertDuration": "5m",
"maxUserSSHCertDuration": "24h",
"enableSSHCA": true
}
}
```
* `type` (mandatory): for a JWK provisioner it must be `JWK`, this field is case
insensitive.
* `name` (mandatory): identifies the provisioner, a good practice is to
use an email address or a descriptive string that allows the identification of
the owner, but it can be any non-empty string.
* `key` (mandatory): is the JWK (JSON Web Key) representation of a public key
used to validate a signed token.
* `encryptedKey` (recommended): is the encrypted private key used to sign a
token. It's a JWE compact string containing the JWK representation of the
private key.
We can use [step](https://github.com/smallstep/cli) to see the private key
encrypted with the password `asdf`:
```sh
$ echo ey...lw | step crypto jwe decrypt | jq
Please enter the password to decrypt the content encryption key:
{
"use": "sig",
"kty": "EC",
"kid": "NPM_9Gz_omTqchS6Xx9Yfvs-EuxkYo6VAk4sL7gyyM4",
"crv": "P-256",
"alg": "ES256",
"x": "bBI5AkO9lwvDuWGfOr0F6ttXC-ZRzJo8kKn5wTzRJXI",
"y": "rcfaqE-EEZgs34Q9SSH3f9Ua5a8dKopXNfEzDD8KRlU",
"d": "rsjCCM_2FQ-uk7nywBEQHl84oaPo4mTpYDgXAu63igE"
}
```
If the ca.json does not contain the encryptedKey, the private key must be
provided using the `--key` flag of the `step ca token` to be able to sign the
token.
### OIDC
An OIDC provisioner allows a user to get a certificate after authenticating
with an OAuth OpenID Connect identity provider. The ID token provided
will be used on the CA authentication, and by default, the certificate will only
have the user's email as a Subject Alternative Name (SAN) Extension.
One of the most common providers and the one we'll use in the following example
is G-Suite.
```json
{
"type": "OIDC",
"name": "Google",
"clientID": "1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com",
"clientSecret": "udTrOT3gzrO7W9fDPgZQLfYJ",
"configurationEndpoint": "https://accounts.google.com/.well-known/openid-configuration",
"admins": ["you@smallstep.com"],
"domains": ["smallstep.com"],
"listenAddress": ":10000",
"claims": {
"maxTLSCertDuration": "8h",
"defaultTLSCertDuration": "2h",
"disableRenewal": true
}
}
```
* `type` (mandatory): indicates the provisioner type and must be `OIDC`.
* `name` (mandatory): a string used to identify the provider when the CLI is
used.
* `clientID` (mandatory): the client id provided by the identity provider used
to initialize the authentication flow.
* `clientSecret` (mandatory): the client secret provided by the identity
provider used to get the id token. Some identity providers might use an empty
string as a secret.
* `configurationEndpoint` (mandatory): is the HTTP address used by the CA to get
the OpenID Connect configuration and public keys used to validate the tokens.
* `admins` (optional): is the list of emails that will be able to get
certificates with custom SANs. If a user is not an admin, it will only be able
to get a certificate with its email in it.
* `domains` (optional): is the list of domains valid. If provided only the
emails with the provided domains will be able to authenticate.
* `listenAddress` (optional): is the loopback address (`:port` or `host:port`)
where the authorization server will redirect to complete the authorization
flow. If it's not defined `step` will use `127.0.0.1` with a random port. This
configuration is only required if the authorization server doesn't allow any
port to be specified at the time of the request for loopback IP redirect URIs.
* `claims` (optional): overwrites the default claims set in the authority, see
the [top](#provisioners) section for all the options.
### X5C
An X5C provisioner allows a client to get an x509 or SSH certificate using
an existing x509 certificate that is trusted by the X5C provisioner.
An X5C provisioner is configured with a root certificate, supplied by the user,
at the time the provisioner is created. The X5C provisioner can authenticate
X5C tokens.
An X5C token is a JWT, signed by the certificate private key, with an `x5c`
header that contains the chain.
If you would like any certificate signed by `step-ca` to become a provisioner,
you can configure the X5C provisioner using the root certificate used by
`step-ca`, like so:
```
step ca provisioner add x5c-smallstep --type X5C --x5c-root $(step path)/certs/root_ca.crt
```
Or you can configure the X5C provisioner with an outside root, giving provisioner
capabilities to a completely separate PKI.
Below is an example of an X5C provisioner in the `ca.json`:
```json
...
{
"type": "X5C",
"name": "x5c",
"roots": "LS0tLS1 ... Q0FURS0tLS0tCg==",
"claims": {
"maxTLSCertDuration": "8h",
"defaultTLSCertDuration": "2h",
"disableRenewal": true
}
}
```
* `type` (mandatory): indicates the provisioner type and must be `X5C`.
* `name` (mandatory): a string used to identify the provider when the CLI is
used.
* `roots` (mandatory): a base64 encoded list of root certificates used for
validating X5C tokens.
* `claims` (optional): overwrites the default claims set in the authority, see
the [top](#provisioners) section for all the options.
### SSHPOP
An SSHPOP provisioner allows a client to renew, revoke, or rekey an SSH
certificate using that certificate for authentication with the CA.
The renew and rekey methods can only be used on SSH host certificates.
An SSHPOP provisioner is configured with the user and host root ssh certificates
from the `ca.json`. The SSHPOP provisioner can only authenticate SSHPOP tokens
generated using SSH certificates created by `step-ca`.
An SSHPOP token is a JWT, signed by the certificate private key, with an `sshpop`
header that contains the SSH certificate.
Below is an example of an SSHPOP provisioner in the `ca.json`:
```json
...
{
"type": "SSHPOP",
"name": "sshpop-smallstep",
"claims": {
"enableSSHCA": true
}
}
```
* `type` (mandatory): indicates the provisioner type and must be `SSHPOP`.
* `name` (mandatory): a string used to identify the provider when the CLI is
used.
* `claims` (optional): overwrites the default claims set in the authority, see
the [top](#provisioners) section for all the options.
### ACME
An ACME provisioner allows a client to request a certificate from the server
using the [ACME Protocol](https://tools.ietf.org/html/rfc8555). The ACME
provisioner can only request X509 certificates. All authentication of the CSR
is managed by the ACME protocol.
Below is an example of an ACME provisioner in the `ca.json`:
```json
...
{
"type": "ACME",
"name": "my-acme-provisioner",
"forceCN": true,
"requireEAB": false,
"claims": {
"maxTLSCertDuration": "8h",
"defaultTLSCertDuration": "2h",
}
}
```
* `type` (mandatory): indicates the provisioner type and must be `ACME`.
* `name` (mandatory): a string used to identify the provider when the CLI is
used.
* `forceCN` (optional): force one of the SANs to become the Common Name, if a
common name is not provided.
* `requireEAB` (optional): require clients to provide External Account Binding
credentials when creating an ACME Account.
* `claims` (optional): overwrites the default claims set in the authority, see
the [top](#provisioners) section for all the options.
See our [`step-ca` ACME tutorial](https://app.smallstep.com/docs/[product]/tutorials/acme-provisioners)
for more guidance on configuring and using the ACME protocol with `step-ca`.
### K8sSA - Kubernetes Service Account
A K8sSA provisioner allows a client to request a certificate from the server
using a Kubernetes Service Account Token.
As of the time when this provisioner was coded, the Kubernetes Service Account
API for retrieving the token from a running instance was still in beta. Therefore,
our K8sSA provisioner must be configured with the public key that will be used
to validate K8sSA tokens.
K8sSA tokens are very minimal. There is no place for SANs, or other details that
a user may want validated in a CSR. It is essentially a bearer token. Therefore,
at this time a K8sSA token can be used to sign a CSR with any SANs. Said
differently, the **K8sSA provisioner does little to no validation on the CSR
before signing it**. You should only configure and use this provisioner if you
know what you are doing. If a malicious user obtains the private key they will
be able to create certificates with any SANs and Subject.
Below is an example of a K8sSA provisioner in the `ca.json`:
```json
...
{
"type": "K8sSA",
"name": "my-kube-provisioner",
"publicKeys": "LS0tLS1...LS0tCg==",
"claims": {
"maxTLSCertDuration": "8h",
"defaultTLSCertDuration": "2h",
}
}
```
* `type` (mandatory): indicates the provisioner type and must be `K8sSA`.
* `name` (mandatory): a string used to identify the provider when the CLI is
used.
* `publicKeys` (mandatory): a base64 encoded list of public keys used to validate
K8sSA tokens.
* `claims` (optional): overwrites the default claims set in the authority, see
the [top](#provisioners) section for all the options.
### Provisioners for Cloud Identities
[Step certificates](https://github.com/smallstep/certificates) can grant
certificates to code running in a machine without any other authentication than
the one provided by the cloud. Usually, this is implemented with some kind of
signed document, but the information contained on them might not be enough to
generate a certificate. Due to this limitation, the cloud identities use by
default a trust model called Trust On First Use (TOFU).
The Trust On First Use model allows the use of more permissive CSRs that can
have custom SANs that cannot be validated. But it comes with the limitation that
you can only grant a certificate once. After this first grant, the same machine
will need to renew the certificate using mTLS, and the CA will block any other
attempt to grant a certificate to that instance.
#### AWS
The AWS provisioner allows granting a certificate to an Amazon EC2 instance
using the [Instance Identity Documents](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html)
The [step](https://github.com/smallstep/cli) CLI will generate a custom JWT
token containing the instance identity document and its signature and the CA
will grant a certificate after validating it.
In the ca.json, an AWS provisioner looks like:
```json
{
"type": "AWS",
"name": "Amazon Web Services",
"accounts": ["1234567890"],
"disableCustomSANs": false,
"disableTrustOnFirstUse": false,
"instanceAge": "1h",
"iidRoots": "/path/to/aws.crt",
"claims": {
"maxTLSCertDuration": "2160h",
"defaultTLSCertDuration": "2160h"
}
}
```
* `type` (mandatory): indicates the provisioner type and must be `AWS`.
* `name` (mandatory): a string used to identify the provider when the CLI is
used.
* `accounts` (optional): the list of AWS account numbers that are allowed to use
this provisioner. If none is specified, all AWS accounts will be valid.
* `disableCustomSANs` (optional): by default custom SANs are valid, but if this
option is set to true only the SANs available in the instance identity
document will be valid, these are the private IP and the DNS
`ip-<private-ip>.<region>.compute.internal`.
* `disableTrustOnFirstUse` (optional): by default only one certificate will be
granted per instance, but if the option is set to true this limit is not set
and different tokens can be used to get different certificates.
* `instanceAge` (optional): the maximum age of an instance to grant a
certificate. The instance age is a string using the duration format.
* `iidRoots` (optional): the path to one or more public certificates in PEM
format used to validate the signature of the instance identity document.
* `claims` (optional): overwrites the default claims set in the authority, see
the [top](#provisioners) section for all the options.
#### GCP
The GCP provisioner grants certificates to Google Compute Engine instance using
its [identity](https://cloud.google.com/compute/docs/instances/verifying-instance-identity)
token. The CA will validate the JWT and grant a certificate.
In the ca.json, a GCP provisioner looks like:
```json
{
"type": "GCP",
"name": "Google Cloud",
"serviceAccounts": ["1234567890"],
"projectIDs": ["project-id"],
"disableCustomSANs": false,
"disableTrustOnFirstUse": false,
"instanceAge": "1h",
"claims": {
"maxTLSCertDuration": "2160h",
"defaultTLSCertDuration": "2160h"
}
}
```
* `type` (mandatory): indicates the provisioner type and must be `GCP`.
* `name` (mandatory): a string used to identify the provider when the CLI is
used.
* `serviceAccounts` (optional): the list of service account numbers that are
allowed to use this provisioner. If none is specified, all service accounts
will be valid.
* `projectIDs` (optional): the list of project identifiers that are allowed to
use this provisioner. If non is specified all project will be valid.
* `disableCustomSANs` (optional): by default custom SANs are valid, but if this
option is set to true only the SANs available in the instance identity
document will be valid, these are the DNS
`<instance-name>.c.<project-id>.internal` and
`<instance-name>.<zone>.c.<project-id>.internal`
* `disableTrustOnFirstUse` (optional): by default only one certificate will be
granted per instance, but if the option is set to true this limit is not set
and different tokens can be used to get different certificates.
* `instanceAge` (optional): the maximum age of an instance to grant a
certificate. The instance age is a string using the duration format.
* `claims` (optional): overwrites the default claims set in the authority, see
the [top](#provisioners) section for all the options.
#### Azure
The Azure provisioner grants certificates to Microsoft Azure instances using
the [managed identities tokens](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token).
The CA will validate the JWT and grant a certificate.
In the ca.json, an Azure provisioner looks like:
```json
{
"type": "Azure",
"name": "Microsoft Azure",
"tenantId": "b17c217c-84db-43f0-babd-e06a71083cda",
"resourceGroups": ["backend", "accounting"],
"audience": "https://management.azure.com/",
"disableCustomSANs": false,
"disableTrustOnFirstUse": false,
"claims": {
"maxTLSCertDuration": "2160h",
"defaultTLSCertDuration": "2160h"
}
}
```
* `type` (mandatory): indicates the provisioner type and must be `Azure`.
* `name` (mandatory): a string used to identify the provider when the CLI is
used.
* `tenantId` (mandatory): the Azure account tenant id for this provisioner. This
id is the Directory ID available in the Azure Active Directory properties.
* `audience` (optional): defaults to `https://management.azure.com/` but it can
be changed if necessary.
* `resourceGroups` (optional): the list of resource group names that are allowed
to use this provisioner. If none is specified, all resource groups will be
valid.
* `disableCustomSANs` (optional): by default custom SANs are valid, but if this
option is set to true only the SANs available in the token will be valid, in
Azure only the virtual machine name is available.
* `disableTrustOnFirstUse` (optional): by default only one certificate will be
granted per instance, but if the option is set to true this limit is not set
and different tokens can be used to get different certificates.
* `claims` (optional): overwrites the default claims set in the authority, see
the [top](#provisioners) section for all the options.

View file

@ -1,409 +0,0 @@
# Frequently Asked Questions
These are some commonly asked questions on the topics of PKI, TLS, X509,
cryptography, threshold-cryptography, etc.
Hopefully we will reduce the amount of hand-waving in these responses as we add
more features to the Step toolkit over time.
> We encourage you to read
> [our blog post on everything relating to PKI](https://smallstep.com/blog/everything-pki.html)
> as we believe it to be a solid resource that answers many of of the questions
> listed below.
## What are TLS & PKI?
TLS stands for *transport layer security*. It used to be called *secure sockets
layer* (or SSL), but technically SSL refers to an older version of the protocol.
Normal TCP connections communicate in plain text, allowing attackers to
eavesdrop and spoof messages. If used properly, TLS provides *confidentiality*
and *integrity* for TCP traffic, ensuring that messages can only be seen by their
intended recipient, and cannot be modified in transit.
TLS is a complicated protocol with lots of options, but the most common mode of
operation establishes a secure channel using *asymmetric cryptography* with
*digital certificates* (or just certificates for short).
First, some quick definitions:
* *Asymmetric cryptography* (a.k.a., public key cryptography) is an underappreciated
gift from mathematics to computer science. It uses a *key pair*: a private key
known only to the recipient of the message, and a public key that can be broadly
distributed, even to adversaries, without compromising security.
* *Digital certificates* are data structures that map a public key to the
well-known name of the owner of the corresponding private key (e.g., a DNS host name).
They *bind* a name to the public key so you can address recipients by name instead of
using public keys directly (which are big random numbers).
Briefly, there are two functions that can be achieved using asymmetric cryptography:
* Messages can be *encrypted* using the public key to ensure that only the
private key holder can *decrypt* them, and
* Messages can be *signed* using the private key so that anyone with the *public
key* knows the message came from the private key holder.
With digital certificates, you can replace "private key holder" with "named entity,"
which makes things a whole lot more useful. It lets you use names, instead of
public keys, to address messages.
PKI stands for *public key infrastructure*. Abstractly, it's a set of policies
and procedures for managing digital certificates (i.e., managing the bindings
between names and public keys). Without proper secure PKI, an attacker can fake
a binding and undermine security.
## What's a certificate authority?
A certificate authority (CA) stores, issues, and signs digital certificates. CAs
have their own key pair, with the private key carefully secured (often offline).
The CA binds its name to its public key by signing a digital certificate using
its own private key (called *self signing*). The CA's self-signed certificate,
or *root certificate*, is distributed to all principals in the system (e.g., all
of the clients and servers in your infrastructure).
So, the CA is tasked with securely binding names to public keys. Here's how that process works.
1. When a named principal wants a certificate, it generates its own key pair.
Nobody else ever needs to know the private key, not even the CA.
2. The principal creates a certificate signing request (CSR), containing its
name and public key (and some other stuff), and submits it to the CA. The CSR is
self-signed, like the root certificate, so the CA knows that the requestor has
the corresponding private key.
3. The CA performs some form of *identity proofing*, certifying that the request
is coming from the principal named in the CSR.
4. Once satisfied, the CA issues a certificate by using its own private key to
sign a certificate binding the name and public key from the CSR.
Certificates signed by the CA are used to securely introduce principals that
don't already know one anothers' public keys. Assuming both principals agree on
a trusted CA, they can exchange digital certificates and authenticate the
signatures to gain some assurance that they are communicating with the named entity.
Technically, smallstep's certificate authority is more than just a certificate
authority. It combines several PKI roles into one simple, flexible package. It
acts as a *registration authority*, accepting requests for digital certificates
and verifying the identity of the requesting entities before establishing bindings.
It also acts as a *central directory* and more generally as a *certificate
management system*, a secure location for storing and distributing key material.
## Why not just use Verisign, Entrust, Let's Encrypt, etc?
The web's *open public key infrastructure* (web PKI), while far from perfect,
is an important foundation for securing the web. So why not use it for securing
communication for your own internal infrastructure? There are several reasons:
* It's expensive to provision certificates from a public CA for all of your services
* Public CAs can't handle client certificates (mutual TLS)
* It's much harder (and more expensive) to revoke or roll certificates from public CAs
* It relies on a third party that can subvert your security
More broadly, the answer is that web PKI was designed for the web. A lot of the
web PKI design decisions aren't appropriate for internal systems.
## How does identity proofing work?
In general, trust will always flow back out to you, the operator of your system.
With that in mind, the simplest form of identity proofing is manual: [describe
token-based manual mechanism here]. As your system grows, this process can become
onerous. Automated identity proofing requires careful coordination between
different parts of your system. Smallstep provides additional tooling, and vetted
designs, to help with this. If you integrate with our other tools its easy to
start with a manual identity proofing mechanism and move to a more sophisticated
automated method as your system grows.
## What are the security risks of exposing the OAuth Client Secret in the output of `step ca provisioner list`?
It would be nice if we could have the CA operate as an OAuth confidential
client, keeping the client secret private and redirecting back to the CA
instead of to loopback. But, to be clear, this is not an abuse of the OAuth
spec. The way this was implemented in step, as an OAuth native application
using a public client, is standard, was intentional, (mostly) conforms to best
current practices, and the flow we're using is widely used in practice. A
confidential client is (strictly?) more secure. But a public client that
redirects to loopback isnt a significant security risk under a normal threat
model.
### The current flow
The advantage of the current flow is that its more general purpose. For
example, `step oauth` works without any additional infrastructure. An issued
access token can be used from the command line, and OIDC identity tokens can be
safely used to authenticate to remote services (including remote services that
dont speak OAuth OIDC, or dont even speak HTTP, but can validate a
JWT). `step-ca` is one example of a remote service that can authenticate
step users via OIDC identity token. You can also use `step crypto jwt verify` to
authenticate using OIDC at the command line.
The particular details of the OAuth flow we selected has pros & cons, as does
any flow. The relevant security risks are:
1. Since the OAuth access token isnt issued directly to a remote server (e.g.,
`step-ca`), remote servers cant safely use the issued access tokens
without significant care. If they did, an attacker might be able to maliciously
trick the remote server into using an access token that was issued to a
different client.
2. The redirect back from the OAuth authorization server to the
client can be intercepted by another process running on the local machine. This
isnt really necessary though, because...
3. The `client_secret` is public, so anyone can initiate (and complete) an OAuth
flow using our client (but it will always redirect back to 127.0.0.1).
The first threat is moot since we don't actually use the access token for
anything when we're connecting to `step-ca`. Unfortunately there's no way to not
get an access token. So we just ignore it.
Note that it *is* safe to use the access token from the command line to access
resources at a remote API. For example, its safe to user `step oauth` to obtain
an OAuth access token from Google and use it to access Googles APIs in a bash
script.
More generally, access tokens are for accessing resources (authorization) and
are not useful for authenticating a user since they're not audience-addressed.
If you and I both have a Google OAuth client, I could get Alice to OAuth into
my app and use the issued access token to masquerade as Alice to you. But OIDC
identity tokens are audience-addressed. An identity token is a JWT with the
`client_id` baked in as the `aud` (audience) parameter. As long as clients check
this parameter (which `step-ca` does) they're not susceptible to this attack. In
fact, OIDC identity tokens were designed and developed precisely to solve this
problem.
So it's completely safe for one entity to obtain an *identity token* from an IdP
on behalf of a user and use it to authenticate to another entity (like `step`
does). That's exactly the use case OIDC was designed to support.
The second and third threats are related. They involve a malicious attempt to
initiate an OAuth OIDC flow using our client credentials. There's a lot of
analysis we could do here comparing this situation to a non-native (e.g., *web*)
client and to other flows (e.g., the *implicit flow*, which also makes the
client secret public). Skipping that detail, we know two things for sure:
1. OAuth flows generally require user consent to complete (e.g., a user has to
"approve" an application's authentication / authorization request)
2. An OAuth flow initiated using our client will always redirect back to 127.0.0.1
So a malicious attacker trying to obtain an *identity token* needs two things:
1. They need to get user consent to complete an OAuth flow
2. They need to have local access to the user's machine
This is already a pretty high bar. Its worth noting, however, that the first
part is *much* easier if the user is already logged in and the identity provider
is configured to not require consent (i.e., the OAuth flow is automatically
completed without the user having to click any buttons). Okta seems to
do this for some applications by default.
It's also worth noting that a process with local access could probably obtain
an access/identity token for a *confidential client* without knowing the client
secret. That's the main reason I don't think the flow we're using has a
meaningful security impact under most threat models. The biggest difference is
that attacking a confidential client would probably require privileged (root)
access, whereas our flow could be attacked by an unprivileged process. But
the fruit of our OAuth flow — the SSH certificate — is also available for
use by an unprivileged process running locally via the `ssh-agent`. So the
only thing possibly gained is the ability to exfiltrate.
### Stuff we should consider doing
There are at least three OAuth features that are relevant to this discussion.
Two have already been mentioned:
1. OAuth *public clients* for *native applications* can be (er, are *supposed*
to be) created without a client secret
2. Proof Key for Code Exchange (PKCE) helps ensure that the process requesting the access token / identity token is the same process that initiated the flow
The first feature, clients without secrets, is mostly cosmetic. There's no real
difference between a public secret and no secret, except that it's confusing to
have something called a "secret" that's not actually secret. (Caveat: IdPs that
support "native applications" without secrets typically enforce other rules for
these clients — they often require PKCE and might not issue a renew token, for
example. But these features can often be turned on/of for other client types,
too.)
The reason we don't assume a *public client* without a secret is that,
unfortunately, not all IdPs support them. Significantly, Google does not. In
fact, gcloud (Google Cloud's CLI tool) uses OAuth OIDC and uses the exact same
technique we're using. If you look at the source you'll find their
"NOTSOSECRET" All of that said, we should support "native clients" without
secrets at some point.
We should also implement Proof Key for Code Exchange (PKCE). This has been on
our backlog for a while, and it's actually really simple and useful. It's
definitely low-hanging fruit. Before initiating the OAuth flow your client
generates a random number. It hashes that number and passes the hash to the IdP
as part of the authorization request (the URL that users are sent to for
login). After authenticating and consenting, when the user is
redirected back to the client, the client makes a request to the IdP to get an
access token & identity token. In *that* request the client must include the
*unhashed* random number. The IdP re-hashes it and compares it to the value it
received in the authorization request. If they match, the IdP can be certain
that the entity making the access token request is the same entity that
initiated the flow. In other words, the request has not been intercepted by
some malicious intermediary.
The last hardening mechanism to be aware of are the `acr` and `amr` parameters.
Basically, when the OAuth flow is initiated the client can request that the IdP
require consent, do 2FA, and a bunch of other stuff. The issued identity token
includes parameters to indicate that these processes did, indeed, occur.
Leveraging this mechanism one could configure `step-ca` to check these parameters
and be sure that users have consented and undergone a 2FA presence check (e.g.,
tapped a security token). Unfortunately, like a bunch of other optional
OAuth features, many IdPs (*cough* Google *cough*) don't support this stuff.
### Summary
Implementing PKCE should be our highest priority item. Support for "native"
clients without secrets would also be nice. Forcing 2FA & consent via `acr` & `amr`
is also a good idea. Support for non-native clients that redirect back to the
CA, and where the secret is *actually* secret, would also be nice. But it's a
bigger architectural change and the security implications aren't actually that
severe.
## I already have PKI in place. Can I use this with my own root certificate?
Yes. There's a easy way, and a longer but more secure way to do this.
### Option 1: The easy way
If you have your root CA signing key available, you can run:
```bash
step ca init --root=[ROOT_CERT_FILE] --key=[ROOT_PRIVATE_KEY_FILE]
```
The root certificate can be in PEM or DER format, and the signing key can be a PEM file containing a PKCS#1, PKCS#8, or RFC5915 (for EC) key.
### Option 2: More secure
That said, CAs are usually pretty locked down and it's bad practice to move the private key around. So I'm gonna assume that's not an option and give you the more complex instructions to do this "the right way", by generating a CSR for `step-ca`, getting it signed by your existing root, and configuring `step-ca` to use it.
When you run `step ca init` we create a couple artifacts under `~/.step/`. The important ones for us are:
- `~/.step/certs/root_ca.crt` is your root CA certificate
- `~/.step/secrets/root_ca_key` is your root CA signing key
- `~/.step/certs/intermediate_ca.crt` is your intermediate CA cert
- `~/.step/secrets/intermediate_ca_key` is the intermediate signing key used by `step-ca`
The easiest thing to do is to run `step ca init` to get this scaffolding configuration in place, then remove/replace these artifacts with new ones that are tied to your existing root CA.
First, `step-ca` does not actually need the root CA signing key. So you can simply remove that file:
```bash
rm ~/.step/secrets/root_ca_key
```
Next, replace `step-ca`'s root CA cert with your existing root certificate:
```bash
mv /path/to/your/existing/root.crt ~/.step/certs/root_ca.crt
```
Now you need to generate a new signing key and intermediate certificate, signed by your existing root CA. To do that we can use the `step certificate create` subcommand to generate a certificate signing request (CSR) that we'll have your existing root CA sign, producing an intermediate certificate.
To generate those artifacts run:
```bash
step certificate create "Intermediate CA Name" intermediate.csr intermediate_ca_key --csr
```
Next, you'll need to transfer the CSR file (`intermediate.csr`) to your existing root CA and get it signed.
Now you need to get the CSR executed by your existing root CA.
**Active Directory Certificate Services**
```bash
certreq -submit -attrib "CertificateTemplate:SubCA" intermediate.csr intermediate.crt
```
**AWS Certificate Manager Private CA**
Here's a Python script that uses [issue-certificate](https://docs.aws.amazon.com/acm-pca/latest/userguide/PcaIssueCert.html) to process the CSR:
```python
import boto3
import sys
AWS_CA_ARN = '[YOUR_PRIVATE_CA_ARN]'
csr = ''.join(sys.stdin.readlines())
client = boto3.client('acm-pca')
response = client.issue_certificate(
CertificateAuthorityArn=AWS_CA_ARN,
Csr=csr,
SigningAlgorithm='SHA256WITHRSA',
TemplateArn='arn:aws:acm-pca:::template/SubordinateCACertificate_PathLen1/V1',
Validity={
'Value': 5,
'Type': 'YEARS'
}
)
print(f"Creating certificate with ARN {response['CertificateArn']}...", file=sys.stderr, end='')
waiter = client.get_waiter('certificate_issued')
waiter.wait(
CertificateAuthorityArn=AWS_CA_ARN,
CertificateArn=response['CertificateArn']
)
print('done.', file=sys.stderr)
response = client.get_certificate(
CertificateArn=response['CertificateArn'],
CertificateAuthorityArn=AWS_CA_ARN
)
print(response['Certificate'])
```
To run it, fill in the ARN of your CA and run:
```bash
python issue_certificate.py < intermediate.csr > intermediate.crt
```
**OpenSSL**
```bash
openssl ca -config [ROOT_CA_CONFIG_FILE] \
-extensions v3_intermediate_ca \
-days 3650 -notext -md sha512 \
-in intermediate.csr \
-out intermediate.crt
```
**CFSSL**
For CFSSL you'll need a signing profile that specifies a 10-year expiry:
```bash
cat > ca-smallstep-config.json <<EOF
{
"signing": {
"profiles": {
"smallstep": {
"expiry": "87660h",
"usages": ["signing"]
}
}
}
}
EOF
```
Now use that config to sign the intermediate certificate:
```bash
cfssl sign -ca ca.pem \
-ca-key ca-key.pem \
-config ca-smallstep-config.json \
-profile smallstep
-csr intermediate.csr | cfssljson -bare
```
This process will yield a signed `intermediate.crt` certificate (or `cert.pem` for CFSSL). Transfer this file back to the machine running `step-ca`.
Finally, replace the intermediate .crt and signing key produced by `step ca init` with the new ones we just created:
```bash
mv intermediate.crt ~/.step/certs/intermediate_ca.crt
mv intermediate_ca_key ~/.step/secrets/intermediate_ca_key
```
That should be it! You should be able to start `step-ca` and the certificates should be trusted by anything that trusts your existing root CA.
## Further Reading
* [Use TLS Everywhere](https://smallstep.com/blog/use-tls.html)
* [Everything you should know about certificates and PKI but are too afraid to ask](https://smallstep.com/blog/everything-pki.html)

View file

@ -1,211 +0,0 @@
# Revocation
**Active Revocation**: A certificate is no longer valid from the moment it has
been actively revoked. Clients are required to check against centralized
sources of certificate validity information (e.g. by using CRLs (Certificate
Revocation Lists) or OCSP (Online Certificate Status Protocol)) to
verify that certificates have not been revoked. Active Revocation requires
clients to take an active role in certificate validation for the benefit of
real time revocation.
**Passive Revocation**: A certificate that has been passively revoked can no
longer be renewed. It will still be valid for the remainder of it's validity period,
but cannot be prolonged. The benefit of passive revocation is that clients
can verify certificates in a simple, decentralized manner without relying on
centralized 3rd parties. Passive revocation works best with short
certificate lifetimes.
`step certificates` currently only supports passive revocation. Active revocation
is on our roadmap.
Run `step help ca revoke` from the command line for full documentation, list of
command line flags, and examples.
## How It Works
Certificates can be created and revoked through the `step cli`. Let's walk
through an example.
### Requirements
* `step` (>=v0.10.0) ([install instructions](../README.md#installation-guide))
### Let's Get To It
1. Bootstrap your PKI.
> If you've already done this before and you have a `$STEPPATH` with certs,
> secrets, and configuration files then you can move on to step 2.
Run `step ca init`.
<pre><code>
<b>$ step ca init --name "Local CA" --provisioner admin --dns localhost --address ":443"</b>
</code></pre>
Move on to step 3.
2. Configure a persistence layer in your `ca.json`.
> If you did step 1 with `step` v0.10.0 or greater then your db will
> have been configured in the previous step.
Get your full step path by running `echo $(step path)`. Now edit
your `ca.json` by adding the following stanza as a top-level attribute:
> Your `ca.json` should be in `$(step path)/config/ca.json`.
```
...
"db": {
"type": "badger",
"dataSource": "<full step path>/db"
},
...
```
Check out our [database documentation](./database.md) to see all available
database backends and adapters.
3. Run the CA
<pre><code>
<b>$ step-ca $(step path)/config/ca.json</b>
</code></pre>
4. Create a certificate for localhost
<pre><code>
<b>$ step ca certificate localhost localhost.crt localhost.key</b>
✔ Key ID: n2kqNhicCCqVxJidspCQrjXWBtGwsa9zk3eBObrViy8 (sebastian@smallstep.com)
✔ Please enter the password to decrypt the provisioner key:
✔ CA: https://ca.smallstep.com
✔ Certificate: localhost.crt
✔ Private Key: localhost.key
<b>$ step certificate inspect --short localhost.crt</b>
X.509v3 TLS Certificate (ECDSA P-256) [Serial: 2400...2409]
Subject: localhost
Issuer: Smallstep Intermediate CA
Provisioner: sebastian@smallstep.com [ID: n2kq...Viy8]
Valid from: 2019-04-23T22:55:54Z
to: 2019-04-24T22:55:54Z
</code></pre>
5. Renew the certificate (just to prove we can!)
<pre><code>
<b>$ step ca renew localhost.crt localhost.key</b>
✔ Would you like to overwrite localhost.crt [y/n]: y
Your certificate has been saved in localhost.crt.
# Make sure the from timestamp is "newer"
<b>$ step certificate inspect --short localhost.crt</b>
X.509v3 TLS Certificate (ECDSA P-256) [Serial: 5963...8406]
Subject: localhost
Issuer: Smallstep Intermediate CA
Provisioner: sebastian@smallstep.com [ID: n2kq...Viy8]
Valid from: 2019-04-23T22:57:50Z
to: 2019-04-24T22:57:50Z
</pre></code>
6. Now let's revoke the certificate
<pre><code>
<b>$ step certificate inspect --format=json localhost.crt | jq .serial_number</b>
"59636004850364466675608080466579278406"
# the serial number is unique
<b>$ step ca revoke 59636004850364466675608080466579278406</b>
✔ Key ID: n2kqNhicCCqVxJidspCQrjXWBtGwsa9zk3eBObrViy8 (sebastian@smallstep.com)
✔ Please enter the password to decrypt the provisioner key:
✔ CA: https://ca.smallstep.com
Certificate with Serial Number 59636004850364466675608080466579278406 has been revoked.
</pre></code>
7. Awesome! But did it work?
<pre><code>
<b>$ step ca renew localhost.crt localhost.key</b>
error renewing certificate: Unauthorized
# log trace from CA:
[...]
WARN[0569] duration="82.782µs" duration-ns=82782
error="renew: certificate has been revoked"
fields.time="2019-04-23T16:03:01-07:00" method=POST
name=ca path=/renew protocol=HTTP/1.1 referer=
remote-address=127.0.0.1 request-id=bivpj9a3q563rpjheh5g
size=40 status=401 user-agent=Go-http-client/1.1 user-id=
[...]
</pre></code>
8. Other ways to revoke a Certificate
Use the certificate and key. This method does not require a provisioner
because it uses the certificate and key to authenticate the request.
<pre><code>
<b>$ step ca revoke --cert localhost.crt --key localhost.key</b>
Certificate with Serial Number 59636004850364466675608080466579278406 has been revoked.
</pre></code>
Or, revoke a certificate in two steps by first creating a revocation token and
then exchanging that token in a revocation request.
<pre><code>
<b>$ TOKEN=$(step ca token --revoke 59636004850364466675608080466579278406)</b>
✔ Key ID: n2kqNhicCCqVxJidspCQrjXWBtGwsa9zk3eBObrViy8 (sebastian@smallstep.com)
✔ Please enter the password to decrypt the provisioner key:
<b>$ echo $TOKEN | step crypto jwt inspect --insecure</b>
{
"header": {
"alg": "ES256",
"kid": "uxEunU9UhUo96lRvKgpEtRevkzbN5Yq88AFFtb1nSGg",
"typ": "JWT"
},
"payload": {
"aud": "https://localhost:443/1.0/revoke",
"exp": 1556395590,
"iat": 1556395290,
"iss": "sebastian@smallstep.com",
"jti": "1f222fc1a22530b7bcd2a40d7308c566c8e49f90413bc350e07bfabc8002b79b",
"nbf": 1556395290,
"sha": "fef4c75a050e1f3a31175ca4f4fdb711cbef1efcd374fcae4700596604eb8e5a",
"sub": "59636004850364466675608080466579278406"
},
"signature": "M1wX0ea3VXwS5rIim0TgtcCXHDtvP1GWD15cJSvVkrHNO6XMYl6m3ZmnWdwMi976msv-n2GTG3h6dJ3j2ImdfQ"
}
<b>$ step ca revoke --token $TOKEN 59636004850364466675608080466579278406</b>
Certificate with Serial Number 59636004850364466675608080466579278406 has been revoked.
</pre></code>
Or, revoke a certificate in offline mode:
<pre><code>
<b>$ step ca revoke --offline 59636004850364466675608080466579278406</b>
Certificate with Serial Number 59636004850364466675608080466579278406 has been revoked.
<b>$ step ca revoke --offline --cert localhost.crt --key localhost.key</b>
Certificate with Serial Number 59636004850364466675608080466579278406 has been revoked.
</pre></code>
> NOTE: you can only revoke a certificate once. Any repeated attempts to revoke
> the same serial number will fail.
Run `step help ca revoke` from the command line for full documentation, list of
command line flags, and examples.
## What's next?
[Use TLS Everywhere](https://smallstep.com/blog/use-tls.html) and let us know
what you think of our tools. Get in touch over
[Twitter](twitter.com/smallsteplabs) or through our
[GitHub Discussions](https://github.com/smallstep/certificates/discussions) to find answers to frequently asked questions.
[Discord](https://bit.ly/step-discord) to chat with us in real time.
## Further Reading
* [Use TLS Everywhere](https://smallstep.com/blog/use-tls.html)
* [Everything you should know about certificates and PKI but are too afraid to ask](https://smallstep.com/blog/everything-pki.html)

58
go.mod
View file

@ -3,16 +3,16 @@ module github.com/smallstep/certificates
go 1.18 go 1.18
require ( require (
cloud.google.com/go v0.105.0 // indirect cloud.google.com/go v0.107.0 // indirect
cloud.google.com/go/longrunning v0.3.0 cloud.google.com/go/longrunning v0.4.0
cloud.google.com/go/security v1.10.0 cloud.google.com/go/security v1.11.0
github.com/Azure/azure-sdk-for-go v67.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go v67.0.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.28 // indirect github.com/Azure/go-autorest/autorest v0.11.28 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 github.com/Masterminds/sprig/v3 v3.2.3
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/aws/aws-sdk-go v1.44.127 // indirect github.com/aws/aws-sdk-go v1.44.132 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/fatih/color v1.9.0 // indirect github.com/fatih/color v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 github.com/fxamacker/cbor/v2 v2.4.0
@ -24,8 +24,8 @@ require (
github.com/google/go-cmp v0.5.9 github.com/google/go-cmp v0.5.9
github.com/google/go-tpm v0.3.3 github.com/google/go-tpm v0.3.3
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/googleapis/gax-go/v2 v2.6.0 github.com/googleapis/gax-go/v2 v2.7.0
github.com/hashicorp/vault/api v1.8.2 github.com/hashicorp/vault/api v1.8.3
github.com/hashicorp/vault/api/auth/approle v0.3.0 github.com/hashicorp/vault/api/auth/approle v0.3.0
github.com/hashicorp/vault/api/auth/kubernetes v0.3.0 github.com/hashicorp/vault/api/auth/kubernetes v0.3.0
github.com/jhump/protoreflect v1.9.0 // indirect github.com/jhump/protoreflect v1.9.0 // indirect
@ -33,7 +33,7 @@ require (
github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-isatty v0.0.13 // indirect
github.com/micromdm/scep/v2 v2.1.0 github.com/micromdm/scep/v2 v2.1.0
github.com/newrelic/go-agent/v3 v3.20.0 github.com/newrelic/go-agent/v3 v3.20.3
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rs/xid v1.4.0 github.com/rs/xid v1.4.0
github.com/ryboe/q v1.0.17 github.com/ryboe/q v1.0.17
@ -42,27 +42,27 @@ require (
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
github.com/smallstep/nosql v0.5.0 github.com/smallstep/nosql v0.5.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
github.com/urfave/cli v1.22.10 github.com/urfave/cli v1.22.12
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
go.step.sm/cli-utils v0.7.5 go.step.sm/cli-utils v0.7.5
go.step.sm/crypto v0.23.0 go.step.sm/crypto v0.23.1
go.step.sm/linkedca v0.19.0-rc.4 go.step.sm/linkedca v0.19.0
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b golang.org/x/crypto v0.5.0
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b golang.org/x/net v0.5.0
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect golang.org/x/sys v0.4.0 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
google.golang.org/api v0.102.0 google.golang.org/api v0.108.0
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
google.golang.org/grpc v1.50.1 google.golang.org/grpc v1.52.0
google.golang.org/protobuf v1.28.1 google.golang.org/protobuf v1.28.1
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
) )
require ( require (
cloud.google.com/go/compute v1.12.1 // indirect cloud.google.com/go/compute v1.14.0 // indirect
cloud.google.com/go/compute/metadata v0.2.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.6.0 // indirect cloud.google.com/go/iam v0.8.0 // indirect
cloud.google.com/go/kms v1.5.0 // indirect cloud.google.com/go/kms v1.6.0 // indirect
filippo.io/edwards25519 v1.0.0 // indirect filippo.io/edwards25519 v1.0.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect
@ -73,14 +73,14 @@ require (
github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/armon/go-metrics v0.3.9 // indirect github.com/armon/go-metrics v0.3.9 // indirect
github.com/armon/go-radix v1.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
@ -93,7 +93,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v0.16.2 // indirect github.com/hashicorp/go-hclog v0.16.2 // indirect
@ -110,9 +110,9 @@ require (
github.com/hashicorp/go-version v1.2.0 // indirect github.com/hashicorp/go-version v1.2.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/vault/sdk v0.6.0 // indirect github.com/hashicorp/vault/sdk v0.7.0 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
github.com/huandu/xstrings v1.3.2 // indirect github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.12 // indirect github.com/imdario/mergo v0.3.12 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.13.0 // indirect github.com/jackc/pgconn v1.13.0 // indirect
@ -137,7 +137,7 @@ require (
github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.6.1 // indirect github.com/rogpeppe/go-internal v1.6.1 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
@ -145,10 +145,10 @@ require (
github.com/thales-e-security/pool v0.0.2 // indirect github.com/thales-e-security/pool v0.0.2 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
go.etcd.io/bbolt v1.3.6 // indirect go.etcd.io/bbolt v1.3.6 // indirect
go.opencensus.io v0.23.0 // indirect go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/text v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

131
go.sum
View file

@ -1,19 +1,19 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww=
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0=
cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/iam v0.6.0 h1:nsqQC88kT5Iwlm4MeNGTpfMWddp6NB/UOLFTH6m1QfQ= cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk=
cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=
cloud.google.com/go/kms v1.5.0 h1:uc58n3b/n/F2yDMJzHMbXORkJSh3fzO4/+jju6eR7Zg= cloud.google.com/go/kms v1.6.0 h1:OWRZzrPmOZUzurjI2FBGtgY2mB1WaJkqhw6oIwSj0Yg=
cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/longrunning v0.4.0 h1:v+X4EwhHl6xE+TG1XgXj4T1XpKKs7ZevcAJ3FOu0YmY=
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= cloud.google.com/go/longrunning v0.4.0/go.mod h1:eF3Qsw58iX/bkKtVjMTYpH0LRjQ2goDkjkNQTlzq/ZM=
cloud.google.com/go/security v1.10.0 h1:KSKzzJMyUoMRQzcz7azIgqAUqxo7rmQ5rYvimMhikqg= cloud.google.com/go/security v1.11.0 h1:155BmlBUj4940GUlvV4rS4VTxXZWDkOSW3GnXc211Cs=
cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= cloud.google.com/go/security v1.11.0/go.mod h1:qL8hSHb3MqXtsVRgSPOt/igsHrs5pWAy0nrP1zl4j5I=
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
@ -45,17 +45,19 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
@ -82,8 +84,8 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.44.127 h1:IoO2VfuIQg1aMXnl8l6OpNUKT4Qq5CnJMOyIWoTYXj0= github.com/aws/aws-sdk-go v1.44.132 h1:+IjL9VoR0OXScQ5gyme9xjcolwUkd3uaH144f4Ao+4s=
github.com/aws/aws-sdk-go v1.44.127/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.132/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -128,8 +130,9 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -271,10 +274,10 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg=
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@ -354,20 +357,21 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E=
github.com/hashicorp/vault/api v1.8.2 h1:C7OL9YtOtwQbTKI9ogB0A1wffRbCN+rH/LLCHO3d8HM= github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o=
github.com/hashicorp/vault/api v1.8.2/go.mod h1:ML8aYzBIhY5m1MD1B2Q0JV89cC85YVH4t5kBaZiyVaE= github.com/hashicorp/vault/api v1.8.3/go.mod h1:4g/9lj9lmuJQMtT6CmVMHC5FW1yENaVv+Nv4ZfG8fAg=
github.com/hashicorp/vault/api/auth/approle v0.3.0 h1:Ib0oCNXsCq/QZhPYtXPzJEbGS5WR/KoZf8c84QoFdkU= github.com/hashicorp/vault/api/auth/approle v0.3.0 h1:Ib0oCNXsCq/QZhPYtXPzJEbGS5WR/KoZf8c84QoFdkU=
github.com/hashicorp/vault/api/auth/approle v0.3.0/go.mod h1:hm51TbjzUkPO0Y17wkrpwOpvyyMRpXJNueTHiG04t3k= github.com/hashicorp/vault/api/auth/approle v0.3.0/go.mod h1:hm51TbjzUkPO0Y17wkrpwOpvyyMRpXJNueTHiG04t3k=
github.com/hashicorp/vault/api/auth/kubernetes v0.3.0 h1:HkaCmTKzcgLa2tjdiAid1rbmyQNmQGHfnmvIIM2WorY= github.com/hashicorp/vault/api/auth/kubernetes v0.3.0 h1:HkaCmTKzcgLa2tjdiAid1rbmyQNmQGHfnmvIIM2WorY=
github.com/hashicorp/vault/api/auth/kubernetes v0.3.0/go.mod h1:l1B4MGtLc+P37MabBQiIhP3qd9agj0vqhETmaQjjC/Y= github.com/hashicorp/vault/api/auth/kubernetes v0.3.0/go.mod h1:l1B4MGtLc+P37MabBQiIhP3qd9agj0vqhETmaQjjC/Y=
github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs=
github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc= github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc=
github.com/hashicorp/vault/sdk v0.7.0 h1:2pQRO40R1etpKkia5fb4kjrdYMx3BHklPxl1pxpxDHg=
github.com/hashicorp/vault/sdk v0.7.0/go.mod h1:KyfArJkhooyba7gYCKSq8v66QdqJmnbAxtV/OX1+JTs=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
@ -531,8 +535,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/newrelic/go-agent/v3 v3.20.0 h1:AiV7kFr4kUQZcoZt45zW+W2+iZE0TUsk5S8Uhk5jnsM= github.com/newrelic/go-agent/v3 v3.20.3 h1:hUBAMq/Y2Y9as5/yxQbf0zNde/X7w58cWZkm2flZIaw=
github.com/newrelic/go-agent/v3 v3.20.0/go.mod h1:rT6ZUxJc5rQbWLyCtjqQCOcfb01lKRFbc1yMQkcboWM= github.com/newrelic/go-agent/v3 v3.20.3/go.mod h1:rT6ZUxJc5rQbWLyCtjqQCOcfb01lKRFbc1yMQkcboWM=
github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
@ -606,8 +610,9 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
@ -669,7 +674,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.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.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -685,8 +689,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8=
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
@ -694,6 +698,7 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@ -704,16 +709,16 @@ go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mI
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.step.sm/cli-utils v0.7.5 h1:jyp6X8k8mN1B0uWJydTid0C++8tQhm2kaaAdXKQQzdk= go.step.sm/cli-utils v0.7.5 h1:jyp6X8k8mN1B0uWJydTid0C++8tQhm2kaaAdXKQQzdk=
go.step.sm/cli-utils v0.7.5/go.mod h1:taSsY8haLmXoXM3ZkywIyRmVij/4Aj0fQbNTlJvv71I= go.step.sm/cli-utils v0.7.5/go.mod h1:taSsY8haLmXoXM3ZkywIyRmVij/4Aj0fQbNTlJvv71I=
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
go.step.sm/crypto v0.23.0 h1:pkkAlQxeDs+7qZ0mWSnN25qbtDm/AH6u0hYlwcmRWng= go.step.sm/crypto v0.23.1 h1:Yr9vlzjGqIKVi88KcpZtEcNTcpDkt1nVR7tumW4h+CU=
go.step.sm/crypto v0.23.0/go.mod h1:sK4iH/xyQDbffE1jCgj5hraVrbdKY9CTs0Lnjskxnk4= go.step.sm/crypto v0.23.1/go.mod h1:djAhDYpNAuWF2LkzbCVcf0JDy1UWgrxR3eQ7pQ8EQ/w=
go.step.sm/linkedca v0.19.0-rc.4 h1:kaBW+xHkRRgMNDa4gWiIj7gBq5yjbJKGlTWYYo5z2KQ= go.step.sm/linkedca v0.19.0 h1:xuagkR35wrJI2gnu6FAM+q3VmjwsHScvGcJsfZ0GdsI=
go.step.sm/linkedca v0.19.0-rc.4/go.mod h1:b7vWPrHfYLEOTSUZitFEcztVCpTc+ileIN85CwEAluM= go.step.sm/linkedca v0.19.0/go.mod h1:b7vWPrHfYLEOTSUZitFEcztVCpTc+ileIN85CwEAluM=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -745,8 +750,9 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -758,6 +764,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -784,9 +791,11 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -800,6 +809,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20170728174421-0f826bdd13b5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -838,22 +848,28 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/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.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -880,6 +896,7 @@ golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -887,8 +904,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I= google.golang.org/api v0.108.0 h1:WVBc/faN0DkKtR43Q/7+tPny9ZoLZdIiAyG5Q9vFClg=
google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -902,8 +919,8 @@ google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dT
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo= google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -921,8 +938,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View file

@ -108,10 +108,10 @@ var DefaultSSHTemplateData = map[string]string{
{{- end }} {{- end }}
{{- if or .User.GOOS "none" | eq "windows" }} {{- if or .User.GOOS "none" | eq "windows" }}
UserKnownHostsFile "{{.User.StepPath}}\ssh\known_hosts" UserKnownHostsFile "{{.User.StepPath}}\ssh\known_hosts"
ProxyCommand C:\Windows\System32\cmd.exe /c step ssh proxycommand{{- if .User.Context }} --context {{ .User.Context }}{{- end }} %r %h %p ProxyCommand C:\Windows\System32\cmd.exe /c step ssh proxycommand{{- if .User.Context }} --context {{ .User.Context }}{{- end }}{{- if .User.Provisioner }} --provisioner {{ .User.Provisioner }}{{- end }} %r %h %p
{{- else }} {{- else }}
UserKnownHostsFile "{{.User.StepPath}}/ssh/known_hosts" UserKnownHostsFile "{{.User.StepPath}}/ssh/known_hosts"
ProxyCommand step ssh proxycommand{{- if .User.Context }} --context {{ .User.Context }}{{- end }} %r %h %p ProxyCommand step ssh proxycommand{{- if .User.Context }} --context {{ .User.Context }}{{- end }}{{- if .User.Provisioner }} --provisioner {{ .User.Provisioner }}{{- end }} %r %h %p
{{- end }} {{- end }}
`, `,