Merge branch 'master' into herman/acme-da-tpm
141
CHANGELOG.md
|
@ -1,41 +1,108 @@
|
|||
# Changelog
|
||||
|
||||
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/)
|
||||
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
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
|
||||
### Security
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.23.1] - 2022-01-10
|
||||
|
||||
### 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
|
||||
X.509 certificates.
|
||||
- Added provisioner webhooks for augmenting template data and authorizing certificate requests before signing.
|
||||
- Added automatic migration of provisioners when enabling remote managment.
|
||||
- Added provisioner webhooks for augmenting template data and authorizing
|
||||
certificate requests before signing.
|
||||
- Added automatic migration of provisioners when enabling remote management.
|
||||
- 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
|
||||
- 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
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed signature algorithm on EC (root) + RSA (intermediate) PKIs.
|
||||
|
||||
## [0.22.0] - 2022-08-26
|
||||
|
||||
### Added
|
||||
|
||||
- Added automatic configuration of Linked RAs.
|
||||
- Send provisioner configuration on Linked RAs.
|
||||
|
||||
### Changed
|
||||
|
||||
- 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
|
||||
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.
|
||||
|
||||
## [0.20.0] - 2022-05-26
|
||||
|
||||
### Added
|
||||
|
||||
- Added Kubernetes auth method for Vault RAs.
|
||||
- Added support for reporting provisioners to linkedca.
|
||||
- Added support for certificate policies on authority level.
|
||||
- Added a Dockerfile with a step-ca build with HSM support.
|
||||
- A few new WithXX methods for instantiating authorities
|
||||
|
||||
### Changed
|
||||
|
||||
- Context usage in HTTP APIs.
|
||||
- Changed authentication for Vault RAs.
|
||||
- Error message returned to client when authenticating with expired certificate.
|
||||
- Strip padding from ACME CSRs.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- HTTP API handler types.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed SSH revocation.
|
||||
- CA client dial context for js/wasm target.
|
||||
- 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.
|
||||
|
||||
## [0.19.0] - 2022-04-19
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for certificate renewals after expiry using the claim `allowRenewalAfterExpiry`.
|
||||
- Added support for `extraNames` in X.509 templates.
|
||||
- Added `armv5` builds.
|
||||
|
@ -77,110 +154,162 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|||
- Added a new `/roots.pem` endpoint to download the CA roots in PEM format.
|
||||
- Added support for Azure `Managed Identity` tokens.
|
||||
- Added support for automatic configuration of linked RAs.
|
||||
- Added support for the `--context` flag. It's now possible to start the
|
||||
- Added support for the `--context` flag. It's now possible to start the
|
||||
CA with `step-ca --context=abc` to use the configuration from context `abc`.
|
||||
When a context has been configured and no configuration file is provided
|
||||
on startup, the configuration for the current context is used.
|
||||
- Added startup info logging and option to skip it (`--quiet`).
|
||||
- Added support for renaming the CA (Common Name).
|
||||
|
||||
### Changed
|
||||
|
||||
- Made SCEP CA URL paths dynamic.
|
||||
- Support two latest versions of Go (1.17, 1.18).
|
||||
- Upgrade go.step.sm/crypto to v0.16.1.
|
||||
- Upgrade go.step.sm/linkedca to v0.15.0.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Go 1.16 support.
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed admin credentials on RAs.
|
||||
- Fixed ACME HTTP-01 challenges for IPv6 identifiers.
|
||||
- Various improvements under the hood.
|
||||
|
||||
### Security
|
||||
|
||||
## [0.18.2] - 2022-03-01
|
||||
|
||||
### Added
|
||||
|
||||
- Added `subscriptionIDs` and `objectIDs` filters to the Azure provisioner.
|
||||
- [NoSQL](https://github.com/smallstep/nosql/pull/21) package allows filtering
|
||||
out database drivers using Go tags. For example, using the Go flag
|
||||
`--tags=nobadger,nobbolt,nomysql` will only compile `step-ca` with the pgx
|
||||
driver for PostgreSQL.
|
||||
|
||||
### Changed
|
||||
|
||||
- IPv6 addresses are normalized as IP addresses instead of hostnames.
|
||||
- More descriptive JWK decryption error message.
|
||||
- Make the X5C leaf certificate available to the templates using `{{ .AuthorizationCrt }}`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- During provisioner add - validate provisioner configuration before storing to DB.
|
||||
|
||||
## [0.18.1] - 2022-02-03
|
||||
|
||||
### Added
|
||||
|
||||
- Support for ACME revocation.
|
||||
- Replace hash function with an RSA SSH CA to "rsa-sha2-256".
|
||||
- Support Nebula provisioners.
|
||||
- Example Ansible configurations.
|
||||
- Support PKCS#11 as a decrypter, as used by SCEP.
|
||||
|
||||
### Changed
|
||||
|
||||
- Automatically create database directory on `step ca init`.
|
||||
- Slightly improve errors reported when a template has invalid content.
|
||||
- Error reporting in logs and to clients.
|
||||
|
||||
### Fixed
|
||||
|
||||
- SCEP renewal using HTTPS on macOS.
|
||||
|
||||
## [0.18.0] - 2021-11-17
|
||||
|
||||
### Added
|
||||
|
||||
- Support for multiple certificate authority contexts.
|
||||
- Support for generating extractable keys and certificates on a pkcs#11 module.
|
||||
|
||||
### Changed
|
||||
|
||||
- Support two latest versions of Go (1.16, 1.17)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- go 1.15 support
|
||||
|
||||
## [0.17.6] - 2021-10-20
|
||||
|
||||
### Notes
|
||||
|
||||
- 0.17.5 failed in CI/CD
|
||||
|
||||
## [0.17.5] - 2021-10-20
|
||||
|
||||
### Added
|
||||
|
||||
- Support for Azure Key Vault as a KMS.
|
||||
- Adapt `pki` package to support key managers.
|
||||
- gocritic linter
|
||||
|
||||
### Fixed
|
||||
|
||||
- gocritic warnings
|
||||
|
||||
## [0.17.4] - 2021-09-28
|
||||
|
||||
### Fixed
|
||||
|
||||
- Support host-only or user-only SSH CA.
|
||||
|
||||
## [0.17.3] - 2021-09-24
|
||||
|
||||
### Added
|
||||
|
||||
- go 1.17 to github action test matrix
|
||||
- Support for CloudKMS RSA-PSS signers without using templates.
|
||||
- Add flags to support individual passwords for the intermediate and SSH keys.
|
||||
- Global support for group admins in the OIDC provisioner.
|
||||
|
||||
### Changed
|
||||
|
||||
- Using go 1.17 for binaries
|
||||
|
||||
### Fixed
|
||||
|
||||
- Upgrade go-jose.v2 to fix a bug in the JWK fingerprint of Ed25519 keys.
|
||||
|
||||
### Security
|
||||
|
||||
- Use cosign to sign and upload signatures for multi-arch Docker container.
|
||||
- Add debian checksum
|
||||
|
||||
## [0.17.2] - 2021-08-30
|
||||
|
||||
### Added
|
||||
|
||||
- Additional way to distinguish Azure IID and Azure OIDC tokens.
|
||||
|
||||
### Security
|
||||
|
||||
- Sign over all goreleaser github artifacts using cosign
|
||||
|
||||
## [0.17.1] - 2021-08-26
|
||||
|
||||
## [0.17.0] - 2021-08-25
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for Linked CAs using protocol buffers and gRPC
|
||||
- `step-ca init` adds support for
|
||||
- configuring a StepCAS RA
|
||||
- configuring a Linked CA
|
||||
- congifuring a `step-ca` using Helm
|
||||
|
||||
### Changed
|
||||
|
||||
- Update badger driver to use v2 by default
|
||||
- Update TLS cipher suites to include 1.3
|
||||
|
||||
### Security
|
||||
|
||||
- Fix key version when SHA512WithRSA is used. There was a typo creating RSA keys with SHA256 digests instead of SHA512.
|
||||
|
|
|
@ -394,6 +394,6 @@ func GetCertificate(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -514,7 +514,7 @@ func TestHandler_GetCertificate(t *testing.T) {
|
|||
assert.Equals(t, res.Header["Content-Type"], []string{"application/problem+json"})
|
||||
} else {
|
||||
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"})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/smallstep/certificates/logging"
|
||||
)
|
||||
|
||||
// StackTracedError is the set of errors implementing the StackTrace function.
|
||||
|
@ -21,16 +19,21 @@ type StackTracedError interface {
|
|||
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
|
||||
// logging.ResponseLogger. If it does not implement it, then writes the error
|
||||
// using the log package.
|
||||
func Error(rw http.ResponseWriter, err error) {
|
||||
rl, ok := rw.(logging.ResponseLogger)
|
||||
fc, ok := rw.(fieldCarrier)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
rl.WithFields(map[string]interface{}{
|
||||
fc.WithFields(map[string]any{
|
||||
"error": err,
|
||||
})
|
||||
|
||||
|
@ -39,8 +42,8 @@ func Error(rw http.ResponseWriter, err error) {
|
|||
}
|
||||
|
||||
var st StackTracedError
|
||||
if !errors.As(err, &st) {
|
||||
rl.WithFields(map[string]interface{}{
|
||||
if errors.As(err, &st) {
|
||||
fc.WithFields(map[string]any{
|
||||
"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
|
||||
// interface.
|
||||
func EnabledResponse(rw http.ResponseWriter, v interface{}) {
|
||||
func EnabledResponse(rw http.ResponseWriter, v any) {
|
||||
type enableLogger interface {
|
||||
ToLog() (interface{}, error)
|
||||
ToLog() (any, error)
|
||||
}
|
||||
|
||||
if el, ok := v.(enableLogger); ok {
|
||||
|
@ -61,8 +64,8 @@ func EnabledResponse(rw http.ResponseWriter, v interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
if rl, ok := rw.(logging.ResponseLogger); ok {
|
||||
rl.WithFields(map[string]interface{}{
|
||||
if rl, ok := rw.(fieldCarrier); ok {
|
||||
rl.WithFields(map[string]any{
|
||||
"response": out,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,43 +1,78 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
pkgerrors "github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/smallstep/certificates/logging"
|
||||
)
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
theError := errors.New("the error")
|
||||
type stackTracedError struct{}
|
||||
|
||||
type args struct {
|
||||
rw http.ResponseWriter
|
||||
err error
|
||||
func (stackTracedError) Error() string {
|
||||
return "a stacktraced 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 {
|
||||
name string
|
||||
args args
|
||||
withFields bool
|
||||
name string
|
||||
error
|
||||
rw http.ResponseWriter
|
||||
isFieldCarrier bool
|
||||
stepDebug bool
|
||||
expectStackTrace bool
|
||||
}{
|
||||
{"normalLogger", args{httptest.NewRecorder(), theError}, false},
|
||||
{"responseLogger", args{logging.NewResponseLogger(httptest.NewRecorder()), theError}, true},
|
||||
{"noLogger", nil, nil, false, false, false},
|
||||
{"noError", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false},
|
||||
{"noErrorDebug", nil, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false},
|
||||
{"anError", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, false, false},
|
||||
{"anErrorDebug", assert.AnError, logging.NewResponseLogger(httptest.NewRecorder()), true, true, false},
|
||||
{"stackTracedError", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true},
|
||||
{"stackTracedErrorDebug", new(stackTracedError), logging.NewResponseLogger(httptest.NewRecorder()), true, true, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
Error(tt.args.rw, tt.args.err)
|
||||
if tt.withFields {
|
||||
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 {
|
||||
t.Error("ResponseWriter does not implement logging.ResponseLogger")
|
||||
}
|
||||
if tt.stepDebug {
|
||||
t.Setenv("STEPDEBUG", "1")
|
||||
} else {
|
||||
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`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -413,13 +413,13 @@ func (a *Authority) init() error {
|
|||
|
||||
// Read root certificates and store them in the certificates map.
|
||||
if len(a.rootX509Certs) == 0 {
|
||||
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root))
|
||||
for i, path := range a.config.Root {
|
||||
crt, err := pemutil.ReadCertificate(path)
|
||||
a.rootX509Certs = make([]*x509.Certificate, 0, len(a.config.Root))
|
||||
for _, path := range a.config.Root {
|
||||
crts, err := pemutil.ReadCertificateBundle(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.rootX509Certs[i] = crt
|
||||
a.rootX509Certs = append(a.rootX509Certs, crts...)
|
||||
}
|
||||
}
|
||||
for _, crt := range a.rootX509Certs {
|
||||
|
@ -434,13 +434,13 @@ func (a *Authority) init() error {
|
|||
|
||||
// Read federated certificates and store them in the certificates map.
|
||||
if len(a.federatedX509Certs) == 0 {
|
||||
a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots))
|
||||
for i, path := range a.config.FederatedRoots {
|
||||
crt, err := pemutil.ReadCertificate(path)
|
||||
a.federatedX509Certs = make([]*x509.Certificate, 0, len(a.config.FederatedRoots))
|
||||
for _, path := range a.config.FederatedRoots {
|
||||
crts, err := pemutil.ReadCertificateBundle(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.federatedX509Certs[i] = crt
|
||||
a.federatedX509Certs = append(a.federatedX509Certs, crts...)
|
||||
}
|
||||
}
|
||||
for _, crt := range a.federatedX509Certs {
|
||||
|
|
|
@ -6,8 +6,10 @@ import (
|
|||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -18,6 +20,7 @@ import (
|
|||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/minica"
|
||||
"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) {
|
||||
auth := testAuthority(t)
|
||||
authWithDatabase, err := New(auth.config, WithDatabase(auth.db))
|
||||
|
|
|
@ -90,6 +90,7 @@ type CRLConfig struct {
|
|||
GenerateOnRevoke bool `json:"generateOnRevoke,omitempty"`
|
||||
CacheDuration *provisioner.Duration `json:"cacheDuration,omitempty"`
|
||||
RenewPeriod *provisioner.Duration `json:"renewPeriod,omitempty"`
|
||||
IDPurl string `json:"idpURL,omitempty"`
|
||||
}
|
||||
|
||||
// IsEnabled returns if the CRL is enabled.
|
||||
|
|
|
@ -265,8 +265,20 @@ func (c *linkedCaClient) GetCertificateData(serial string) (*db.CertificateData,
|
|||
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{
|
||||
Provisioner: pd,
|
||||
RaInfo: raInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -385,16 +385,13 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
|
|||
}
|
||||
|
||||
var data sshutil.TemplateData
|
||||
var principals []string
|
||||
|
||||
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)
|
||||
if v, err := unsafeParseSigned(token); err == nil {
|
||||
data.SetToken(v)
|
||||
}
|
||||
|
||||
principals = nil
|
||||
} else {
|
||||
// Get the identity using either the default identityFunc or one injected
|
||||
// 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 {
|
||||
data.AddCriticalOption(k, v)
|
||||
}
|
||||
|
||||
principals = iden.Usernames
|
||||
}
|
||||
|
||||
// Use the default template unless no-templates are configured and email is
|
||||
|
@ -446,8 +441,7 @@ func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption
|
|||
})
|
||||
} else {
|
||||
signOptions = append(signOptions, sshCertOptionsValidator(SignSSHOptions{
|
||||
CertType: SSHUserCert,
|
||||
Principals: principals,
|
||||
CertType: SSHUserCert,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -582,6 +582,9 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) {
|
|||
{"ok-principals", p1, args{t1, SignSSHOptions{Principals: []string{"name"}}, pub},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"name", "name@smallstep.com"},
|
||||
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},
|
||||
&SignSSHOptions{CertType: "user", Principals: []string{"max", "mariano"},
|
||||
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},
|
||||
{"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-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-sshCA-disabled", p6, args{"foo", SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false},
|
||||
// Missing parametrs
|
||||
|
|
|
@ -773,10 +773,17 @@ func (a *Authority) GenerateCertificateRevocationList() error {
|
|||
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.
|
||||
//
|
||||
// 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 {
|
||||
revocationList.ExtraExtensions = []pkix.Extension{
|
||||
{Id: oidExtensionIssuingDistributionPoint, Value: b},
|
||||
|
|
|
@ -44,6 +44,9 @@ type AdminClient struct {
|
|||
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
|
||||
// AdminError returned by the CA.
|
||||
type AdminClientError struct {
|
||||
|
@ -137,6 +140,28 @@ func (c *AdminClient) retryOnError(r *http.Response) bool {
|
|||
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.
|
||||
func (c *AdminClient) GetAdmin(id string) (*linkedca.Admin, error) {
|
||||
var retried bool
|
||||
|
@ -144,7 +169,7 @@ func (c *AdminClient) GetAdmin(id string) (*linkedca.Admin, error) {
|
|||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -227,7 +252,7 @@ func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*adminAPI.GetAdmin
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -282,7 +307,7 @@ func (c *AdminClient) CreateAdmin(createAdminRequest *adminAPI.CreateAdminReques
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -314,7 +339,7 @@ func (c *AdminClient) RemoveAdmin(id string) error {
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "client DELETE %s failed", u)
|
||||
return clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
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))
|
||||
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)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client PUT %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
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)
|
||||
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)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
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)
|
||||
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)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -502,7 +527,7 @@ func (c *AdminClient) RemoveProvisioner(opts ...ProvisionerOption) error {
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "client DELETE %s failed", u)
|
||||
return clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -534,7 +559,7 @@ func (c *AdminClient) CreateProvisioner(prov *linkedca.Provisioner) (*linkedca.P
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -570,7 +595,7 @@ func (c *AdminClient) UpdateProvisioner(name string, prov *linkedca.Provisioner)
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "client PUT %s failed", u)
|
||||
return clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -609,7 +634,7 @@ func (c *AdminClient) GetExternalAccountKeysPaginate(provisionerName, reference
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -645,7 +670,7 @@ func (c *AdminClient) CreateExternalAccountKey(provisionerName string, eakReques
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -677,7 +702,7 @@ func (c *AdminClient) RemoveExternalAccountKey(provisionerName, keyID string) er
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "client DELETE %s failed", u)
|
||||
return clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -704,7 +729,7 @@ func (c *AdminClient) GetAuthorityPolicy() (*linkedca.Policy, error) {
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client GET %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -739,7 +764,7 @@ func (c *AdminClient) CreateAuthorityPolicy(p *linkedca.Policy) (*linkedca.Polic
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client POST %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -774,7 +799,7 @@ func (c *AdminClient) UpdateAuthorityPolicy(p *linkedca.Policy) (*linkedca.Polic
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client PUT %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -805,7 +830,7 @@ func (c *AdminClient) RemoveAuthorityPolicy() error {
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client DELETE %s failed: %w", u, err)
|
||||
return clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -832,7 +857,7 @@ func (c *AdminClient) GetProvisionerPolicy(provisionerName string) (*linkedca.Po
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client GET %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -867,7 +892,7 @@ func (c *AdminClient) CreateProvisionerPolicy(provisionerName string, p *linkedc
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client POST %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -902,7 +927,7 @@ func (c *AdminClient) UpdateProvisionerPolicy(provisionerName string, p *linkedc
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client PUT %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -933,7 +958,7 @@ func (c *AdminClient) RemoveProvisionerPolicy(provisionerName string) error {
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client DELETE %s failed: %w", u, err)
|
||||
return clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -967,7 +992,7 @@ func (c *AdminClient) GetACMEPolicy(provisionerName, reference, keyID string) (*
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client GET %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -1009,7 +1034,7 @@ func (c *AdminClient) CreateACMEPolicy(provisionerName, reference, keyID string,
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client POST %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -1051,7 +1076,7 @@ func (c *AdminClient) UpdateACMEPolicy(provisionerName, reference, keyID string,
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client PUT %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -1089,7 +1114,7 @@ func (c *AdminClient) RemoveACMEPolicy(provisionerName, reference, keyID string)
|
|||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client DELETE %s failed: %w", u, err)
|
||||
return clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -1120,7 +1145,7 @@ retry:
|
|||
req.Header.Add("Authorization", tok)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client POST %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -1155,7 +1180,7 @@ retry:
|
|||
req.Header.Add("Authorization", tok)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("client PUT %s failed: %w", u, err)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
@ -1186,7 +1211,7 @@ retry:
|
|||
req.Header.Add("Authorization", tok)
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("client DELETE %s failed: %w", u, err)
|
||||
return clientError(err)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
|
|
|
@ -61,7 +61,7 @@ func Bootstrap(token string) (*Client, error) {
|
|||
// }
|
||||
// resp, err := client.Get("https://internal.smallstep.com")
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
|
||||
b, err := createBootstrap(token)
|
||||
b, err := createBootstrap(token) //nolint:contextcheck // deeply nested context; temporary
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ func BootstrapServer(ctx context.Context, token string, base *http.Server, optio
|
|||
// ... // register services
|
||||
// srv.Serve(lis)
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
411
ca/client.go
|
@ -2,6 +2,7 @@ package ca
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -74,18 +76,26 @@ func (c *uaClient) SetTransport(tr http.RoundTripper) {
|
|||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
return nil, errors.Wrapf(err, "create POST %s request failed", u)
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
req.Header.Set("User-Agent", UserAgent)
|
||||
|
@ -580,18 +590,24 @@ func (c *Client) SetTransport(tr http.RoundTripper) {
|
|||
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.
|
||||
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
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/version"})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
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 !retried && c.retryOnError(resp) {
|
||||
if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
|
@ -604,18 +620,24 @@ retry:
|
|||
return &version, nil
|
||||
}
|
||||
|
||||
// Health performs the health request to the CA and returns the
|
||||
// api.HealthResponse struct.
|
||||
// Health performs the health request to the CA with an empty context
|
||||
// and returns the api.HealthResponse struct.
|
||||
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
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/health"})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
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 !retried && c.retryOnError(resp) {
|
||||
if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
|
@ -628,21 +650,29 @@ retry:
|
|||
return &health, nil
|
||||
}
|
||||
|
||||
// Root performs the root request to the CA with the given 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.
|
||||
// Root 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) 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
|
||||
sha256Sum = strings.ToLower(strings.ReplaceAll(sha256Sum, "-", ""))
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/root/" + sha256Sum})
|
||||
retry:
|
||||
resp, err := newInsecureClient().Get(u.String())
|
||||
resp, err := newInsecureClient().GetWithContext(ctx, u.String())
|
||||
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 !retried && c.retryOnError(resp) {
|
||||
if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
|
@ -660,9 +690,15 @@ retry:
|
|||
return &root, nil
|
||||
}
|
||||
|
||||
// Sign performs the sign request to the CA and returns the api.SignResponse
|
||||
// struct.
|
||||
// Sign performs the sign request to the CA with an empty context and returns
|
||||
// the api.SignResponse struct.
|
||||
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
|
||||
body, err := json.Marshal(req)
|
||||
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"})
|
||||
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 {
|
||||
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.Sign; client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -691,19 +727,30 @@ retry:
|
|||
return &sign, nil
|
||||
}
|
||||
|
||||
// Renew performs the renew request to the CA and returns the api.SignResponse
|
||||
// struct.
|
||||
// Renew performs the renew request to the CA with an empty context and
|
||||
// returns the api.SignResponse struct.
|
||||
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
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"})
|
||||
client := &http.Client{Transport: tr}
|
||||
retry:
|
||||
resp, err := client.Post(u.String(), "application/json", http.NoBody)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", u.String(), http.NoBody)
|
||||
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 !retried && c.retryOnError(resp) {
|
||||
if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
|
@ -717,23 +764,30 @@ retry:
|
|||
}
|
||||
|
||||
// RenewWithToken performs the renew request to the CA with the given
|
||||
// authorization token and returns the api.SignResponse struct. This method is
|
||||
// generally used to renew an expired certificate.
|
||||
// authorization token and and empty context and returns the api.SignResponse struct.
|
||||
// This method is generally used to renew an expired certificate.
|
||||
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
|
||||
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 {
|
||||
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)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
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 !retried && c.retryOnError(resp) {
|
||||
if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
|
@ -746,24 +800,34 @@ retry:
|
|||
return &sign, nil
|
||||
}
|
||||
|
||||
// Rekey performs the rekey request to the CA and returns the api.SignResponse
|
||||
// struct.
|
||||
// Rekey performs the rekey request to the CA with an empty context and
|
||||
// returns the api.SignResponse struct.
|
||||
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
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error marshaling request")
|
||||
}
|
||||
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/rekey"})
|
||||
client := &http.Client{Transport: tr}
|
||||
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 {
|
||||
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 !retried && c.retryOnError(resp) {
|
||||
if !retried && c.retryOnError(resp) { //nolint:contextcheck // deeply nested context; retry using the same context
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
|
@ -776,9 +840,15 @@ retry:
|
|||
return &sign, nil
|
||||
}
|
||||
|
||||
// Revoke performs the revoke request to the CA and returns the api.RevokeResponse
|
||||
// struct.
|
||||
// Revoke performs the revoke request to the CA with an empty context and returns
|
||||
// the api.RevokeResponse struct.
|
||||
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
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
|
@ -793,12 +863,12 @@ retry:
|
|||
}
|
||||
|
||||
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 {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -811,12 +881,21 @@ retry:
|
|||
return &revoke, nil
|
||||
}
|
||||
|
||||
// Provisioners performs the provisioners request to the CA and returns the
|
||||
// api.ProvisionersResponse struct with a map of provisioners.
|
||||
// Provisioners performs the provisioners request to the CA with an empty 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) 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
|
||||
o := new(ProvisionerOptions)
|
||||
if err := o.Apply(opts); err != nil {
|
||||
|
@ -827,12 +906,12 @@ func (c *Client) Provisioners(opts ...ProvisionerOption) (*api.ProvisionersRespo
|
|||
RawQuery: o.rawQuery(),
|
||||
})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -845,19 +924,26 @@ retry:
|
|||
return &provisioners, nil
|
||||
}
|
||||
|
||||
// ProvisionerKey performs the request to the CA to get the encrypted key for
|
||||
// the given provisioner kid and returns the api.ProvisionerKeyResponse struct
|
||||
// with the encrypted key.
|
||||
// ProvisionerKey performs the request to the CA with an empty context to get
|
||||
// the encrypted key for the given provisioner kid and returns the api.ProvisionerKeyResponse
|
||||
// struct with the encrypted key.
|
||||
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
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/provisioners/" + kid + "/encrypted-key"})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -870,18 +956,24 @@ retry:
|
|||
return &key, nil
|
||||
}
|
||||
|
||||
// Roots performs the get roots request to the CA and returns the
|
||||
// api.RootsResponse struct.
|
||||
// Roots performs the get roots request to the CA with an empty context
|
||||
// and returns the api.RootsResponse struct.
|
||||
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
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/roots"})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -894,18 +986,24 @@ retry:
|
|||
return &roots, nil
|
||||
}
|
||||
|
||||
// Federation performs the get federation request to the CA and returns the
|
||||
// api.FederationResponse struct.
|
||||
// Federation performs the get federation request to the CA with an empty context
|
||||
// and returns the api.FederationResponse struct.
|
||||
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
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/federation"})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -918,9 +1016,15 @@ retry:
|
|||
return &federation, nil
|
||||
}
|
||||
|
||||
// SSHSign performs the POST /ssh/sign request to the CA and returns the
|
||||
// api.SSHSignResponse struct.
|
||||
// SSHSign performs the POST /ssh/sign request to the CA with an empty context
|
||||
// and returns the api.SSHSignResponse struct.
|
||||
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
|
||||
body, err := json.Marshal(req)
|
||||
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"})
|
||||
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 {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -946,9 +1050,15 @@ retry:
|
|||
return &sign, nil
|
||||
}
|
||||
|
||||
// SSHRenew performs the POST /ssh/renew request to the CA and returns the
|
||||
// api.SSHRenewResponse struct.
|
||||
// SSHRenew performs the POST /ssh/renew request to the CA with an empty context
|
||||
// and returns the api.SSHRenewResponse struct.
|
||||
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
|
||||
body, err := json.Marshal(req)
|
||||
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"})
|
||||
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 {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -974,9 +1084,15 @@ retry:
|
|||
return &renew, nil
|
||||
}
|
||||
|
||||
// SSHRekey performs the POST /ssh/rekey request to the CA and returns the
|
||||
// api.SSHRekeyResponse struct.
|
||||
// SSHRekey performs the POST /ssh/rekey request to the CA with an empty context
|
||||
// and returns the api.SSHRekeyResponse struct.
|
||||
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
|
||||
body, err := json.Marshal(req)
|
||||
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"})
|
||||
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 {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -1002,9 +1118,15 @@ retry:
|
|||
return &rekey, nil
|
||||
}
|
||||
|
||||
// SSHRevoke performs the POST /ssh/revoke request to the CA and returns the
|
||||
// api.SSHRevokeResponse struct.
|
||||
// SSHRevoke performs the POST /ssh/revoke request to the CA with an empty context
|
||||
// and returns the api.SSHRevokeResponse struct.
|
||||
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
|
||||
body, err := json.Marshal(req)
|
||||
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"})
|
||||
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 {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -1030,18 +1152,24 @@ retry:
|
|||
return &revoke, nil
|
||||
}
|
||||
|
||||
// SSHRoots performs the GET /ssh/roots request to the CA and returns the
|
||||
// api.SSHRootsResponse struct.
|
||||
// SSHRoots performs the GET /ssh/roots request to the CA with an empty context
|
||||
// and returns the api.SSHRootsResponse struct.
|
||||
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
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/roots"})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -1054,18 +1182,24 @@ retry:
|
|||
return &keys, nil
|
||||
}
|
||||
|
||||
// SSHFederation performs the get /ssh/federation request to the CA and returns
|
||||
// the api.SSHRootsResponse struct.
|
||||
// SSHFederation performs the get /ssh/federation request to the CA with an empty context
|
||||
// and returns the api.SSHRootsResponse struct.
|
||||
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
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/federation"})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -1078,9 +1212,15 @@ retry:
|
|||
return &keys, nil
|
||||
}
|
||||
|
||||
// SSHConfig performs the POST /ssh/config request to the CA to get the ssh
|
||||
// configuration templates.
|
||||
// SSHConfig performs the POST /ssh/config request to the CA with an empty context
|
||||
// to get the ssh configuration templates.
|
||||
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
|
||||
body, err := json.Marshal(req)
|
||||
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"})
|
||||
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 {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -1106,9 +1246,15 @@ retry:
|
|||
return &cfg, nil
|
||||
}
|
||||
|
||||
// SSHCheckHost performs the POST /ssh/check-host request to the CA with the
|
||||
// given principal.
|
||||
// SSHCheckHost performs the POST /ssh/check-host request to the CA with an empty context,
|
||||
// the principal and a token and returns the api.SSHCheckPrincipalResponse.
|
||||
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
|
||||
body, err := json.Marshal(&api.SSHCheckPrincipalRequest{
|
||||
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"})
|
||||
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 {
|
||||
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client POST %s failed",
|
||||
[]interface{}{u, errs.WithMessage("Failed to perform POST request to %s", u)}...)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -1141,17 +1286,22 @@ retry:
|
|||
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) {
|
||||
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
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/ssh/hosts"})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -1164,8 +1314,13 @@ retry:
|
|||
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) {
|
||||
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
|
||||
body, err := json.Marshal(req)
|
||||
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"})
|
||||
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 {
|
||||
return nil, errors.Wrapf(err, "client.SSHBastion; client POST %s failed", u)
|
||||
return nil, clientError(err)
|
||||
}
|
||||
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
|
||||
goto retry
|
||||
}
|
||||
|
@ -1192,13 +1347,18 @@ retry:
|
|||
}
|
||||
|
||||
// RootFingerprint is a helper method that returns the current root fingerprint.
|
||||
// It does an health connection and gets the fingerprint from the TLS verified
|
||||
// chains.
|
||||
// It does an health connection and gets the fingerprint from the TLS verified chains.
|
||||
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"})
|
||||
resp, err := c.client.Get(u.String())
|
||||
resp, err := c.client.GetWithContext(ctx, u.String())
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "client GET %s failed", u)
|
||||
return "", clientError(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.TLS == nil || len(resp.TLS.VerifiedChains) == 0 {
|
||||
|
@ -1353,3 +1513,12 @@ func readError(r io.ReadCloser) error {
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
|
34
ca/tls.go
|
@ -24,6 +24,10 @@ import (
|
|||
// getDefaultTransport.
|
||||
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() {
|
||||
// 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
|
||||
|
@ -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
|
||||
|
@ -108,7 +135,7 @@ func (c *Client) getClientTLSConfig(ctx context.Context, sign *api.SignResponse,
|
|||
//nolint:staticcheck // Use mutable tls.Config on renew
|
||||
tr.DialTLS = c.buildDialTLS(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
|
||||
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
|
||||
tr.DialTLS = c.buildDialTLS(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
|
||||
c.SetTransport(tr)
|
||||
|
@ -279,7 +306,8 @@ func getDefaultTLSConfig(sign *api.SignResponse) *tls.Config {
|
|||
func getDefaultDialer() *net.Dialer {
|
||||
// With the KeepAlive parameter set to 0, it will be use Golang's default.
|
||||
return &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
Timeout: 30 * time.Second,
|
||||
LocalAddr: localAddr,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package stepcas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -37,7 +38,7 @@ type stepIssuer interface {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -46,7 +47,7 @@ func newStepIssuer(caURL *url.URL, client *ca.Client, iss *apiv1.CertificateIssu
|
|||
case "x5c":
|
||||
return newX5CIssuer(caURL, iss)
|
||||
case "jwk":
|
||||
return newJWKIssuer(caURL, client, iss)
|
||||
return newJWKIssuer(ctx, caURL, client, iss)
|
||||
default:
|
||||
return nil, errors.Errorf("stepCAS `certificateIssuer.type` %s is not supported", iss.Type)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package stepcas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
@ -118,7 +119,7 @@ func Test_newStepIssuer(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
t.Errorf("newStepIssuer() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package stepcas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
@ -21,13 +22,13 @@ type jwkIssuer struct {
|
|||
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 signer jose.Signer
|
||||
// Read the key from the CA if not provided.
|
||||
// Or read it from a PEM file.
|
||||
if cfg.Key == "" {
|
||||
p, err := findProvisioner(client, provisioner.TypeJWK, cfg.Provisioner)
|
||||
p, err := findProvisioner(ctx, client, provisioner.TypeJWK, cfg.Provisioner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -144,10 +145,10 @@ func newJWKSignerFromEncryptedKey(kid, key, password string) (jose.Signer, error
|
|||
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 := ""
|
||||
for {
|
||||
ps, err := client.Provisioners(ca.WithProvisionerCursor(cursor))
|
||||
ps, err := client.ProvisionersWithContext(ctx, ca.WithProvisionerCursor(cursor))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ func New(ctx context.Context, opts apiv1.Options) (*StepCAS, error) {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
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.
|
||||
// This avoid the request for the password if not provided.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@ func testJWKIssuer(t *testing.T, caURL *url.URL, password string) *jwkIssuer {
|
|||
key = testEncryptedKeyPath
|
||||
password = testPassword
|
||||
}
|
||||
jwk, err := newJWKIssuer(caURL, client, &apiv1.CertificateIssuer{
|
||||
jwk, err := newJWKIssuer(context.TODO(), caURL, client, &apiv1.CertificateIssuer{
|
||||
Type: "jwk",
|
||||
Provisioner: "ra@doe.org",
|
||||
Key: key,
|
||||
|
|
|
@ -121,7 +121,13 @@ func appAction(ctx *cli.Context) error {
|
|||
|
||||
// Allow custom contexts.
|
||||
if caCtx := ctx.String("context"); caCtx != "" {
|
||||
if err := step.Contexts().SetCurrent(caCtx); err != nil {
|
||||
if _, ok := step.Contexts().Get(caCtx); ok {
|
||||
if err := step.Contexts().SetCurrent(caCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if token == "" {
|
||||
return fmt.Errorf("context %q not found", caCtx)
|
||||
} else if err := createContext(caCtx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -234,6 +240,26 @@ To get a linked authority token:
|
|||
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
|
||||
// code 1. If the environment variable STEPDEBUG is set to 1 it shows the
|
||||
// stack trace of the error.
|
||||
|
|
|
@ -7,7 +7,6 @@ RUN apk add --no-cache curl git make
|
|||
RUN make V=1 download
|
||||
RUN make V=1 bin/step-ca bin/step-awskms-init bin/step-cloudkms-init
|
||||
|
||||
|
||||
FROM smallstep/step-cli:latest
|
||||
|
||||
COPY --from=builder /src/bin/step-ca /usr/local/bin/step-ca
|
||||
|
|
|
@ -19,7 +19,7 @@ function init_if_possible () {
|
|||
fi
|
||||
done
|
||||
if [ ${missing_vars} = 1 ]; then
|
||||
>&2 echo "there is no ca.json config file; please run step ca init, or provide config parameters via DOCKER_STEPCA_INIT_ vars"
|
||||
>&2 echo "there is no ca.json config file; please run step ca init, or provide config parameters via DOCKER_STEPCA_INIT_ vars"
|
||||
else
|
||||
step_ca_init "${@}"
|
||||
fi
|
||||
|
@ -34,31 +34,52 @@ function generate_password () {
|
|||
|
||||
# Initialize a CA if not already initialized
|
||||
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=(
|
||||
--name "${DOCKER_STEPCA_INIT_NAME}"
|
||||
--dns "${DOCKER_STEPCA_INIT_DNS_NAMES}"
|
||||
--provisioner "${DOCKER_STEPCA_INIT_PROVISIONER_NAME:-admin}"
|
||||
--password-file "${STEPPATH}/password"
|
||||
--dns "${DOCKER_STEPCA_INIT_DNS_NAMES}"
|
||||
--provisioner "${DOCKER_STEPCA_INIT_PROVISIONER_NAME}"
|
||||
--password-file "${STEPPATH}/password"
|
||||
--provisioner-password-file "${STEPPATH}/provisioner_password"
|
||||
--address ":9000"
|
||||
)
|
||||
if [ -n "${DOCKER_STEPCA_INIT_PASSWORD}" ]; then
|
||||
echo "${DOCKER_STEPCA_INIT_PASSWORD}" > "${STEPPATH}/password"
|
||||
echo "${DOCKER_STEPCA_INIT_PASSWORD}" > "${STEPPATH}/provisioner_password"
|
||||
else
|
||||
generate_password > "${STEPPATH}/password"
|
||||
generate_password > "${STEPPATH}/provisioner_password"
|
||||
fi
|
||||
if [ -n "${DOCKER_STEPCA_INIT_SSH}" ]; then
|
||||
if [ "${DOCKER_STEPCA_INIT_SSH}" == "true" ]; then
|
||||
setup_args=("${setup_args[@]}" --ssh)
|
||||
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[@]}"
|
||||
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
|
||||
}
|
||||
|
||||
if [ -f /usr/sbin/pcscd ]; then
|
||||
/usr/sbin/pcscd
|
||||
/usr/sbin/pcscd
|
||||
fi
|
||||
|
||||
if [ ! -f "${STEPPATH}/config/ca.json" ]; then
|
||||
init_if_possible
|
||||
init_if_possible
|
||||
fi
|
||||
|
||||
exec "${@}"
|
||||
|
|
|
@ -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
|
|
@ -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)
|
161
docs/acme.md
|
@ -1,161 +0,0 @@
|
|||
# Using ACME with `step-ca `
|
||||
|
||||
Let’s assume you’ve [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 you’ve [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.
|
||||
|
||||
That’s 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 CA’s root certificate
|
||||
|
||||
Once certificates are issued, you’ll also need to ensure they’re renewed before
|
||||
they expire.
|
||||
|
||||
### Pointing Clients at the right ACME Directory URL
|
||||
|
||||
Most ACME clients connect to Let’s 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 CA’s root certificate
|
||||
|
||||
Communication between an ACME client and server [always uses
|
||||
HTTPS](https://tools.ietf.org/html/rfc8555#section-6.1). By default, client’s
|
||||
will validate the server’s HTTPS certificate using the public root certificates
|
||||
in your system’s [default
|
||||
trust](https://smallstep.com/blog/everything-pki.html#trust-stores) store.
|
||||
That’s fine when you’re connecting to Let’s Encrypt: it’s a public CA and its
|
||||
root certificate is in your system’s default trust store already. Your internal
|
||||
root certificate isn’t, 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 system’s default trust store (e.g.,
|
||||
using [`step certificate
|
||||
install`](https://smallstep.com/docs/cli/certificate/install/))
|
||||
|
||||
If you’re using your CA for TLS in production, explicitly configuring your ACME
|
||||
client to only trust your root certificate is a better option. We’ll
|
||||
demonstrate this method with several clients below.
|
||||
|
||||
If you’re simulating Let’s 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 system’s 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/), it’s 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).
|
217
docs/cas.md
|
@ -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
|
||||
```
|
106
docs/database.md
|
@ -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.
|
227
docs/defaults.md
|
@ -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.
|
186
docs/docker.md
|
@ -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.
|
Before Width: | Height: | Size: 572 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 6.2 MiB |
Before Width: | Height: | Size: 9.5 MiB |
237
docs/kms.md
|
@ -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.
|
|
@ -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.
|
|
@ -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 isn’t a significant security risk under a normal threat
|
||||
model.
|
||||
|
||||
### The current flow
|
||||
The advantage of the current flow is that it’s 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
|
||||
don’t speak OAuth OIDC, or don’t 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 isn’t issued directly to a remote server (e.g.,
|
||||
`step-ca`), remote servers can’t 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
|
||||
isn’t 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, it’s safe to user `step oauth` to obtain
|
||||
an OAuth access token from Google and use it to access Google’s 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. It’s 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)
|
|
@ -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
|
@ -3,16 +3,16 @@ module github.com/smallstep/certificates
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.105.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.3.0
|
||||
cloud.google.com/go/security v1.10.0
|
||||
cloud.google.com/go v0.107.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.4.0
|
||||
cloud.google.com/go/security v1.11.0
|
||||
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/azure/auth v0.5.11 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
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/fatih/color v1.9.0 // indirect
|
||||
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-tpm v0.3.3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/googleapis/gax-go/v2 v2.6.0
|
||||
github.com/hashicorp/vault/api v1.8.2
|
||||
github.com/googleapis/gax-go/v2 v2.7.0
|
||||
github.com/hashicorp/vault/api v1.8.3
|
||||
github.com/hashicorp/vault/api/auth/approle v0.3.0
|
||||
github.com/hashicorp/vault/api/auth/kubernetes v0.3.0
|
||||
github.com/jhump/protoreflect v1.9.0 // indirect
|
||||
|
@ -33,7 +33,7 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/micromdm/scep/v2 v2.1.0
|
||||
github.com/newrelic/go-agent/v3 v3.20.0
|
||||
github.com/newrelic/go-agent/v3 v3.20.3
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/xid v1.4.0
|
||||
github.com/ryboe/q v1.0.17
|
||||
|
@ -42,27 +42,27 @@ require (
|
|||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
|
||||
github.com/smallstep/nosql v0.5.0
|
||||
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.step.sm/cli-utils v0.7.5
|
||||
go.step.sm/crypto v0.23.0
|
||||
go.step.sm/linkedca v0.19.0-rc.4
|
||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b
|
||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect
|
||||
go.step.sm/crypto v0.23.1
|
||||
go.step.sm/linkedca v0.19.0
|
||||
golang.org/x/crypto v0.5.0
|
||||
golang.org/x/net v0.5.0
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
google.golang.org/api v0.102.0
|
||||
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect
|
||||
google.golang.org/grpc v1.50.1
|
||||
google.golang.org/api v0.108.0
|
||||
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
|
||||
google.golang.org/grpc v1.52.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.12.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
||||
cloud.google.com/go/iam v0.6.0 // indirect
|
||||
cloud.google.com/go/kms v1.5.0 // indirect
|
||||
cloud.google.com/go/compute v1.14.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v0.8.0 // indirect
|
||||
cloud.google.com/go/kms v1.6.0 // indirect
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
|
@ -73,14 +73,14 @@ require (
|
|||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // 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-radix v1.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/badger v1.6.2 // 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/protobuf v1.5.2 // 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/go-cleanhttp v0.5.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/golang-lru v0.5.4 // 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/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.13.0 // indirect
|
||||
|
@ -137,7 +137,7 @@ require (
|
|||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // 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/shopspring/decimal v1.2.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/x448/float16 v0.8.4 // 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
|
||||
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
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
|
131
go.sum
|
@ -1,19 +1,19 @@
|
|||
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.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
|
||||
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
|
||||
cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
|
||||
cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
|
||||
cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
|
||||
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
|
||||
cloud.google.com/go/iam v0.6.0 h1:nsqQC88kT5Iwlm4MeNGTpfMWddp6NB/UOLFTH6m1QfQ=
|
||||
cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=
|
||||
cloud.google.com/go/kms v1.5.0 h1:uc58n3b/n/F2yDMJzHMbXORkJSh3fzO4/+jju6eR7Zg=
|
||||
cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=
|
||||
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
|
||||
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
|
||||
cloud.google.com/go/security v1.10.0 h1:KSKzzJMyUoMRQzcz7azIgqAUqxo7rmQ5rYvimMhikqg=
|
||||
cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA=
|
||||
cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww=
|
||||
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
|
||||
cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0=
|
||||
cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/iam v0.8.0 h1:E2osAkZzxI/+8pZcxVLcDtAQx/u+hZXVryUaYQ5O0Kk=
|
||||
cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=
|
||||
cloud.google.com/go/kms v1.6.0 h1:OWRZzrPmOZUzurjI2FBGtgY2mB1WaJkqhw6oIwSj0Yg=
|
||||
cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
|
||||
cloud.google.com/go/longrunning v0.4.0 h1:v+X4EwhHl6xE+TG1XgXj4T1XpKKs7ZevcAJ3FOu0YmY=
|
||||
cloud.google.com/go/longrunning v0.4.0/go.mod h1:eF3Qsw58iX/bkKtVjMTYpH0LRjQ2goDkjkNQTlzq/ZM=
|
||||
cloud.google.com/go/security v1.11.0 h1:155BmlBUj4940GUlvV4rS4VTxXZWDkOSW3GnXc211Cs=
|
||||
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/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
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/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
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/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.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
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.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
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.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
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/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
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/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.44.127 h1:IoO2VfuIQg1aMXnl8l6OpNUKT4Qq5CnJMOyIWoTYXj0=
|
||||
github.com/aws/aws-sdk-go v1.44.127/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aws/aws-sdk-go v1.44.132 h1:+IjL9VoR0OXScQ5gyme9xjcolwUkd3uaH144f4Ao+4s=
|
||||
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/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=
|
||||
|
@ -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/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 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.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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||
github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU=
|
||||
github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
|
||||
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
|
||||
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/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=
|
||||
|
@ -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/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.2 h1:C7OL9YtOtwQbTKI9ogB0A1wffRbCN+rH/LLCHO3d8HM=
|
||||
github.com/hashicorp/vault/api v1.8.2/go.mod h1:ML8aYzBIhY5m1MD1B2Q0JV89cC85YVH4t5kBaZiyVaE=
|
||||
github.com/hashicorp/vault/api v1.8.3 h1:cHQOLcMhBR+aVI0HzhPxO62w2+gJhIrKguQNONPzu6o=
|
||||
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/go.mod h1:hm51TbjzUkPO0Y17wkrpwOpvyyMRpXJNueTHiG04t3k=
|
||||
github.com/hashicorp/vault/api/auth/kubernetes v0.3.0 h1:HkaCmTKzcgLa2tjdiAid1rbmyQNmQGHfnmvIIM2WorY=
|
||||
github.com/hashicorp/vault/api/auth/kubernetes v0.3.0/go.mod h1:l1B4MGtLc+P37MabBQiIhP3qd9agj0vqhETmaQjjC/Y=
|
||||
github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs=
|
||||
github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc=
|
||||
github.com/hashicorp/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/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
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.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/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.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.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/newrelic/go-agent/v3 v3.20.0 h1:AiV7kFr4kUQZcoZt45zW+W2+iZE0TUsk5S8Uhk5jnsM=
|
||||
github.com/newrelic/go-agent/v3 v3.20.0/go.mod h1:rT6ZUxJc5rQbWLyCtjqQCOcfb01lKRFbc1yMQkcboWM=
|
||||
github.com/newrelic/go-agent/v3 v3.20.3 h1:hUBAMq/Y2Y9as5/yxQbf0zNde/X7w58cWZkm2flZIaw=
|
||||
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/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
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.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.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.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 v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
|
@ -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.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.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk=
|
||||
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8=
|
||||
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/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
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.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.2/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.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
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.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
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.step.sm/cli-utils v0.7.5 h1:jyp6X8k8mN1B0uWJydTid0C++8tQhm2kaaAdXKQQzdk=
|
||||
go.step.sm/cli-utils v0.7.5/go.mod h1:taSsY8haLmXoXM3ZkywIyRmVij/4Aj0fQbNTlJvv71I=
|
||||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||
go.step.sm/crypto v0.23.0 h1:pkkAlQxeDs+7qZ0mWSnN25qbtDm/AH6u0hYlwcmRWng=
|
||||
go.step.sm/crypto v0.23.0/go.mod h1:sK4iH/xyQDbffE1jCgj5hraVrbdKY9CTs0Lnjskxnk4=
|
||||
go.step.sm/linkedca v0.19.0-rc.4 h1:kaBW+xHkRRgMNDa4gWiIj7gBq5yjbJKGlTWYYo5z2KQ=
|
||||
go.step.sm/linkedca v0.19.0-rc.4/go.mod h1:b7vWPrHfYLEOTSUZitFEcztVCpTc+ileIN85CwEAluM=
|
||||
go.step.sm/crypto v0.23.1 h1:Yr9vlzjGqIKVi88KcpZtEcNTcpDkt1nVR7tumW4h+CU=
|
||||
go.step.sm/crypto v0.23.1/go.mod h1:djAhDYpNAuWF2LkzbCVcf0JDy1UWgrxR3eQ7pQ8EQ/w=
|
||||
go.step.sm/linkedca v0.19.0 h1:xuagkR35wrJI2gnu6FAM+q3VmjwsHScvGcJsfZ0GdsI=
|
||||
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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
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-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-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
|
||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
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/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=
|
||||
|
@ -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.3.0/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-20180530234432-1e491301e022/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-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-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU=
|
||||
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
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-20190226205417-e64efc72b421/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-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-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-20180823144017-11551d06cbcc/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-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-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-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM=
|
||||
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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-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.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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
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-20190308202827-9d24e82272b4/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-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
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-20190513163551-3ee3066db522/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-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.102.0 h1:JxJl2qQ85fRMPNvlZY/enexbxpCjLwGhZUtgfGeQ51I=
|
||||
google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=
|
||||
google.golang.org/api v0.108.0 h1:WVBc/faN0DkKtR43Q/7+tPny9ZoLZdIiAyG5Q9vFClg=
|
||||
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.2.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-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-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo=
|
||||
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 h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
|
||||
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.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
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.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
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.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
|
||||
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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
|
|
@ -108,10 +108,10 @@ var DefaultSSHTemplateData = map[string]string{
|
|||
{{- end }}
|
||||
{{- if or .User.GOOS "none" | eq "windows" }}
|
||||
UserKnownHostsFile "{{.User.StepPath}}\ssh\known_hosts"
|
||||
ProxyCommand C:\Windows\System32\cmd.exe /c step ssh proxycommand{{- if .User.Context }} --context {{ .User.Context }}{{- end }} %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 }}
|
||||
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 }}
|
||||
`,
|
||||
|
||||
|
|