Merge branch 'master' into herman/ignore-empty-acme-meta

pull/1136/head
Herman Slatman 2 years ago
commit 02d679e160
No known key found for this signature in database
GPG Key ID: F4D8A44EA0A75A4F

@ -13,10 +13,14 @@ jobs:
create_release:
name: Create Release
#needs: ci
runs-on: ubuntu-20.04
needs: ci
runs-on: ubuntu-latest
env:
DOCKER_IMAGE: smallstep/step-ca
outputs:
version: ${{ steps.extract-tag.outputs.VERSION }}
is_prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }}
docker_tags: ${{ env.DOCKER_TAGS }}
steps:
- name: Is Pre-release
id: is_prerelease
@ -25,7 +29,17 @@ jobs:
echo ${{ github.ref }} | grep "\-rc.*"
OUT=$?
if [ $OUT -eq 0 ]; then IS_PRERELEASE=true; else IS_PRERELEASE=false; fi
echo "::set-output name=IS_PRERELEASE::${IS_PRERELEASE}"
echo "IS_PRERELEASE=${IS_PRERELEASE}" >> ${GITHUB_OUTPUT}
- name: Extract Tag Names
id: extract-tag
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=${VERSION}" >> ${GITHUB_OUTPUT}
echo "DOCKER_TAGS=${{ env.DOCKER_IMAGE }}:${VERSION}" >> ${GITHUB_ENV}
- name: Add Latest Tag
if: steps.is_prerelease.outputs.IS_PRERELEASE == 'false'
run: |
echo "DOCKER_TAGS=${{ env.DOCKER_TAGS }},${{ env.DOCKER_IMAGE }}:latest" >> ${GITHUB_ENV}
- name: Create Release
id: create_release
uses: actions/create-release@v1
@ -39,8 +53,11 @@ jobs:
goreleaser:
name: Upload Assets To Github w/ goreleaser
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: create_release
permissions:
id-token: write
contents: write
steps:
- name: Checkout
uses: actions/checkout@v3
@ -50,17 +67,14 @@ jobs:
go-version: 1.19
check-latest: true
- name: Install cosign
uses: sigstore/cosign-installer@v2.7.0
uses: sigstore/cosign-installer@v2
with:
cosign-release: 'v1.12.1'
- name: Write cosign key to disk
id: write_key
run: echo "${{ secrets.COSIGN_KEY }}" > "/tmp/cosign.key"
cosign-release: 'v1.13.1'
- name: Get Release Date
id: release_date
run: |
RELEASE_DATE=$(date +"%y-%m-%d")
echo "::set-output name=RELEASE_DATE::${RELEASE_DATE}"
echo "RELEASE_DATE=${RELEASE_DATE}" >> ${GITHUB_ENV}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
with:
@ -68,34 +82,19 @@ jobs:
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GORELEASER_PAT }}
COSIGN_PWD: ${{ secrets.COSIGN_PWD }}
RELEASE_DATE: ${{ steps.release_date.outputs.RELEASE_DATE }}
RELEASE_DATE: ${{ env.RELEASE_DATE }}
COSIGN_EXPERIMENTAL: 1
build_upload_docker:
name: Build & Upload Docker Images
runs-on: ubuntu-20.04
needs: ci
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
check-latest: true
- name: Install cosign
uses: sigstore/cosign-installer@v1.1.0
with:
cosign-release: 'v1.1.0'
- name: Write cosign key to disk
id: write_key
run: echo "${{ secrets.COSIGN_KEY }}" > "/tmp/cosign.key"
- name: Build
id: build
run: |
PATH=$PATH:/usr/local/go/bin:/home/admin/go/bin
make docker-artifacts
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
COSIGN_PWD: ${{ secrets.COSIGN_PWD }}
needs: create_release
permissions:
id-token: write
contents: write
uses: smallstep/workflows/.github/workflows/docker-buildx-push.yml@main
with:
platforms: linux/amd64,linux/386,linux/arm,linux/arm64
tags: ${{ needs.create_release.outputs.docker_tags }}
docker_image: smallstep/step-ca
docker_file: docker/Dockerfile.step-ca
secrets: inherit

@ -87,8 +87,9 @@ checksum:
signs:
- cmd: cosign
stdin: '{{ .Env.COSIGN_PWD }}'
args: ["sign-blob", "-key=/tmp/cosign.key", "-output-signature=${signature}", "${artifact}"]
signature: "${artifact}.sig"
certificate: "${artifact}.pem"
args: ["sign-blob", "--oidc-issuer=https://token.actions.githubusercontent.com", "--output-certificate=${certificate}", "--output-signature=${signature}", "${artifact}"]
artifacts: all
snapshot:
@ -153,9 +154,9 @@ release:
Below is an example using `cosign` to verify a release artifact:
```
cosign verify-blob \
-key https://raw.githubusercontent.com/smallstep/certificates/master/cosign.pub \
-signature ~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz.sig
COSIGN_EXPERIMENTAL=1 cosign verify-blob \
--certificate ~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz.sig.pem \
--signature ~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz.sig \
~/Downloads/step-ca_darwin_{{ .Version }}_amd64.tar.gz
```

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
read -r firstline < .VERSION
last_half="${firstline##*tag: }"
if [[ ${last_half::1} == "v" ]]; then

@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- 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 experimental support for CRLs.
### Fixed
- MySQL DSN parsing issues fixed with upgrade to [smallstep/nosql@v0.5.0](https://github.com/smallstep/nosql/releases/tag/v0.5.0).

@ -79,8 +79,6 @@ $(info DEB_VERSION is $(DEB_VERSION))
$(info PUSHTYPE is $(PUSHTYPE))
endif
include make/docker.mk
#########################################
# Build
#########################################
@ -232,11 +230,3 @@ debian: changelog
distclean: clean
.PHONY: changelog debian distclean
#################################################
# Targets for creating step artifacts
#################################################
docker-artifacts: docker-$(PUSHTYPE)
.PHONY: docker-artifacts

@ -44,6 +44,18 @@ const (
DEVICEATTEST01 ChallengeType = "device-attest-01"
)
var (
// InsecurePortHTTP01 is the port used to verify http-01 challenges. If not set it
// defaults to 80.
InsecurePortHTTP01 int
// InsecurePortTLSALPN01 is the port used to verify tls-alpn-01 challenges. If not
// set it defaults to 443.
//
// This variable can be used for testing purposes.
InsecurePortTLSALPN01 int
)
// Challenge represents an ACME response Challenge type.
type Challenge struct {
ID string `json:"-"`
@ -93,6 +105,12 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey,
func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey) error {
u := &url.URL{Scheme: "http", Host: http01ChallengeHost(ch.Value), Path: fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Token)}
// Append insecure port if set.
// Only used for testing purposes.
if InsecurePortHTTP01 != 0 {
u.Host += ":" + strconv.Itoa(InsecurePortHTTP01)
}
vc := MustClientFromContext(ctx)
resp, err := vc.Get(u.String())
if err != nil {
@ -165,7 +183,14 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
InsecureSkipVerify: true, //nolint:gosec // we expect a self-signed challenge certificate
}
hostPort := net.JoinHostPort(ch.Value, "443")
var hostPort string
// Allow to change TLS port for testing purposes.
if port := InsecurePortTLSALPN01; port == 0 {
hostPort = net.JoinHostPort(ch.Value, "443")
} else {
hostPort = net.JoinHostPort(ch.Value, strconv.Itoa(port))
}
vc := MustClientFromContext(ctx)
conn, err := vc.TLSDial("tcp", hostPort, config)

@ -24,6 +24,7 @@ import (
"net/http"
"net/http/httptest"
"reflect"
"strconv"
"strings"
"testing"
"time"
@ -370,6 +371,47 @@ func TestChallenge_Validate(t *testing.T) {
},
}
},
"ok/http-01-insecure": func(t *testing.T) test {
t.Cleanup(func() {
InsecurePortHTTP01 = 0
})
ch := &Challenge{
ID: "chID",
Status: StatusPending,
Type: "http-01",
Token: "token",
Value: "zap.internal",
}
InsecurePortHTTP01 = 8080
return test{
ch: ch,
vc: &mockClient{
get: func(url string) (*http.Response, error) {
return nil, errors.New("force")
},
},
db: &MockDB{
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equals(t, updch.ID, ch.ID)
assert.Equals(t, updch.Token, ch.Token)
assert.Equals(t, updch.Type, ch.Type)
assert.Equals(t, updch.Status, ch.Status)
assert.Equals(t, updch.Value, ch.Value)
err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal:8080/.well-known/acme-challenge/%s: force", ch.Token)
assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
assert.Equals(t, updch.Error.Type, err.Type)
assert.Equals(t, updch.Error.Detail, err.Detail)
assert.Equals(t, updch.Error.Status, err.Status)
assert.Equals(t, updch.Error.Detail, err.Detail)
return nil
},
},
}
},
"fail/dns-01": func(t *testing.T) test {
ch := &Challenge{
ID: "chID",
@ -501,6 +543,72 @@ func TestChallenge_Validate(t *testing.T) {
srv, tlsDial := newTestTLSALPNServer(cert)
srv.Start()
return test{
ch: ch,
vc: &mockClient{
tlsDial: tlsDial,
},
db: &MockDB{
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equals(t, updch.ID, ch.ID)
assert.Equals(t, updch.Token, ch.Token)
assert.Equals(t, updch.Status, ch.Status)
assert.Equals(t, updch.Type, ch.Type)
assert.Equals(t, updch.Value, ch.Value)
assert.Equals(t, updch.Error, nil)
return nil
},
},
srv: srv,
jwk: jwk,
}
},
"ok/tls-alpn-01-insecure": func(t *testing.T) test {
t.Cleanup(func() {
InsecurePortTLSALPN01 = 0
})
ch := &Challenge{
ID: "chID",
Token: "token",
Type: "tls-alpn-01",
Status: StatusPending,
Value: "zap.internal",
}
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
expKeyAuth, err := KeyAuthorization(ch.Token, jwk)
assert.FatalError(t, err)
expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth))
cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], false, true, ch.Value)
assert.FatalError(t, err)
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
t.Fatalf("failed to listen on a port: %v", err)
}
}
_, port, err := net.SplitHostPort(l.Addr().String())
if err != nil {
t.Fatalf("failed to split host port: %v", err)
}
// Use an insecure port
InsecurePortTLSALPN01, err = strconv.Atoi(port)
if err != nil {
t.Fatalf("failed to convert port to int: %v", err)
}
srv, tlsDial := newTestTLSALPNServer(cert, func(srv *httptest.Server) {
srv.Listener.Close()
srv.Listener = l
})
srv.Start()
return test{
ch: ch,
vc: &mockClient{
@ -1248,7 +1356,7 @@ func TestDNS01Validate(t *testing.T) {
type tlsDialer func(network, addr string, config *tls.Config) (conn *tls.Conn, err error)
func newTestTLSALPNServer(validationCert *tls.Certificate) (*httptest.Server, tlsDialer) {
func newTestTLSALPNServer(validationCert *tls.Certificate, opts ...func(*httptest.Server)) (*httptest.Server, tlsDialer) {
srv := httptest.NewUnstartedServer(http.NewServeMux())
srv.Config.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){
@ -1273,6 +1381,11 @@ func newTestTLSALPNServer(validationCert *tls.Certificate) (*httptest.Server, tl
},
}
// Apply options
for _, fn := range opts {
fn(srv)
}
srv.Listener = tls.NewListener(srv.Listener, srv.TLS)
//srv.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // hush

@ -49,6 +49,7 @@ type Authority interface {
GetRoots() ([]*x509.Certificate, error)
GetFederation() ([]*x509.Certificate, error)
Version() authority.Version
GetCertificateRevocationList() ([]byte, error)
}
// mustAuthority will be replaced on unit tests.
@ -267,6 +268,7 @@ func Route(r Router) {
r.MethodFunc("POST", "/renew", Renew)
r.MethodFunc("POST", "/rekey", Rekey)
r.MethodFunc("POST", "/revoke", Revoke)
r.MethodFunc("GET", "/crl", CRL)
r.MethodFunc("GET", "/provisioners", Provisioners)
r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", ProvisionerKey)
r.MethodFunc("GET", "/roots", Roots)

@ -199,6 +199,7 @@ type mockAuthority struct {
getEncryptedKey func(kid string) (string, error)
getRoots func() ([]*x509.Certificate, error)
getFederation func() ([]*x509.Certificate, error)
getCRL func() ([]byte, error)
signSSH func(ctx context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
signSSHAddUser func(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
renewSSH func(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error)
@ -212,6 +213,14 @@ type mockAuthority struct {
version func() authority.Version
}
func (m *mockAuthority) GetCertificateRevocationList() ([]byte, error) {
if m.getCRL != nil {
return m.getCRL()
}
return m.ret1.([]byte), m.err
}
// TODO: remove once Authorize is deprecated.
func (m *mockAuthority) Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error) {
if m.authorize != nil {
@ -772,6 +781,45 @@ func (m *mockProvisioner) AuthorizeSSHRekey(ctx context.Context, token string) (
return m.ret1.(*ssh.Certificate), m.ret2.([]provisioner.SignOption), m.err
}
func Test_CRLGeneration(t *testing.T) {
tests := []struct {
name string
err error
statusCode int
expected []byte
}{
{"empty", nil, http.StatusOK, nil},
}
chiCtx := chi.NewRouteContext()
req := httptest.NewRequest("GET", "http://example.com/crl", nil)
req = req.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockMustAuthority(t, &mockAuthority{ret1: tt.expected, err: tt.err})
w := httptest.NewRecorder()
CRL(w, req)
res := w.Result()
if res.StatusCode != tt.statusCode {
t.Errorf("caHandler.CRL StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
}
body, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Errorf("caHandler.Root unexpected error = %v", err)
}
if tt.statusCode == 200 {
if !bytes.Equal(bytes.TrimSpace(body), tt.expected) {
t.Errorf("caHandler.Root CRL = %s, wants %s", body, tt.expected)
}
}
})
}
}
func Test_caHandler_Route(t *testing.T) {
type fields struct {
Authority Authority

@ -0,0 +1,32 @@
package api
import (
"encoding/pem"
"net/http"
"github.com/smallstep/certificates/api/render"
)
// CRL is an HTTP handler that returns the current CRL in DER or PEM format
func CRL(w http.ResponseWriter, r *http.Request) {
crlBytes, err := mustAuthority(r.Context()).GetCertificateRevocationList()
if err != nil {
render.Error(w, err)
return
}
_, formatAsPEM := r.URL.Query()["pem"]
if formatAsPEM {
pemBytes := pem.EncodeToMemory(&pem.Block{
Type: "X509 CRL",
Bytes: crlBytes,
})
w.Header().Add("Content-Type", "application/x-pem-file")
w.Header().Add("Content-Disposition", "attachment; filename=\"crl.pem\"")
w.Write(pemBytes)
} else {
w.Header().Add("Content-Type", "application/pkix-crl")
w.Header().Add("Content-Disposition", "attachment; filename=\"crl.der\"")
w.Write(crlBytes)
}
}

@ -73,7 +73,12 @@ type Authority struct {
sshCAUserFederatedCerts []ssh.PublicKey
sshCAHostFederatedCerts []ssh.PublicKey
// Do not re-initialize
// CRL vars
crlTicker *time.Ticker
crlStopper chan struct{}
crlMutex sync.Mutex
// If true, do not re-initialize
initOnce bool
startTime time.Time
@ -91,8 +96,11 @@ type Authority struct {
adminMutex sync.RWMutex
// Do Not initialize the authority
// If true, do not initialize the authority
skipInit bool
// If true, do not output initialization logs
quietInit bool
}
// Info contains information about the authority.
@ -600,20 +608,74 @@ func (a *Authority) init() error {
return admin.WrapErrorISE(err, "error loading provisioners to initialize authority")
}
if len(provs) == 0 && !strings.EqualFold(a.config.AuthorityConfig.DeploymentType, "linked") {
// Create First Provisioner
prov, err := CreateFirstProvisioner(ctx, a.adminDB, string(a.password))
if err != nil {
return admin.WrapErrorISE(err, "error creating first provisioner")
// Migration will currently only be kicked off once, because either one or more provisioners
// are migrated or a default JWK provisioner will be created in the DB. It won't run for
// linked or hosted deployments. Not for linked, because that case is explicitly checked
// for above. Not for hosted, because there'll be at least an existing OIDC provisioner.
var firstJWKProvisioner *linkedca.Provisioner
if len(a.config.AuthorityConfig.Provisioners) > 0 {
// Existing provisioners detected; try migrating them to DB storage.
a.initLogf("Starting migration of provisioners")
for _, p := range a.config.AuthorityConfig.Provisioners {
lp, err := ProvisionerToLinkedca(p)
if err != nil {
return admin.WrapErrorISE(err, "error transforming provisioner %q while migrating", p.GetName())
}
// Store the provisioner to be migrated
if err := a.adminDB.CreateProvisioner(ctx, lp); err != nil {
return admin.WrapErrorISE(err, "error creating provisioner %q while migrating", p.GetName())
}
// Mark the first JWK provisioner, so that it can be used for administration purposes
if firstJWKProvisioner == nil && lp.Type == linkedca.Provisioner_JWK {
firstJWKProvisioner = lp
a.initLogf("Migrated JWK provisioner %q with admin permissions", p.GetName())
} else {
a.initLogf("Migrated %s provisioner %q", p.GetType(), p.GetName())
}
}
c := a.config
if c.WasLoadedFromFile() {
// The provisioners in the configuration file can be deleted from
// the file by editing it. Automatic rewriting of the file was considered
// to be too surprising for users and not the right solution for all
// use cases, so we leave it up to users to this themselves.
a.initLogf("Provisioners that were migrated can now be removed from `ca.json` by editing it")
}
a.initLogf("Finished migrating provisioners")
}
// Create first JWK provisioner for remote administration purposes if none exists yet
if firstJWKProvisioner == nil {
firstJWKProvisioner, err = CreateFirstProvisioner(ctx, a.adminDB, string(a.password))
if err != nil {
return admin.WrapErrorISE(err, "error creating first provisioner")
}
a.initLogf("Created JWK provisioner %q with admin permissions", firstJWKProvisioner.GetName())
}
// Create first admin
// Create first super admin, belonging to the first JWK provisioner
// TODO(hs): pass a user-provided first super admin subject to here. With `ca init` it's
// added to the DB immediately if using remote management. But when migrating from
// ca.json to the DB, this option doesn't exist. Adding a flag just to do it during
// migration isn't nice. We could opt for a user to change it afterwards. There exist
// cases in which creation of `step` could lock out a user from API access. This is the
// case if `step` isn't allowed to be signed by Name Constraints or the X.509 policy.
// We have protection for that when creating and updating a policy, but if a policy or
// Name Constraints are in use at the time of migration, that could lock the user out.
superAdminSubject := "step"
if err := a.adminDB.CreateAdmin(ctx, &linkedca.Admin{
ProvisionerId: prov.Id,
Subject: "step",
ProvisionerId: firstJWKProvisioner.Id,
Subject: superAdminSubject,
Type: linkedca.Admin_SUPER_ADMIN,
}); err != nil {
return admin.WrapErrorISE(err, "error creating first admin")
}
a.initLogf("Created super admin %q for JWK provisioner %q", superAdminSubject, firstJWKProvisioner.GetName())
}
}
@ -654,6 +716,18 @@ func (a *Authority) init() error {
a.templates.Data["Step"] = tmplVars
}
// Start the CRL generator, we can assume the configuration is validated.
if a.config.CRL.IsEnabled() {
// Default cache duration to the default one
if v := a.config.CRL.CacheDuration; v == nil || v.Duration <= 0 {
a.config.CRL.CacheDuration = config.DefaultCRLCacheDuration
}
// Start CRL generator
if err := a.startCRLGenerator(); err != nil {
return err
}
}
// JWT numeric dates are seconds.
a.startTime = time.Now().Truncate(time.Second)
// Set flag indicating that initialization has been completed, and should
@ -663,6 +737,14 @@ func (a *Authority) init() error {
return nil
}
// initLogf is used to log initialization information. The output
// can be disabled by starting the CA with the `--quiet` flag.
func (a *Authority) initLogf(format string, v ...any) {
if !a.quietInit {
log.Printf(format, v...)
}
}
// GetID returns the define authority id or a zero uuid.
func (a *Authority) GetID() string {
const zeroUUID = "00000000-0000-0000-0000-000000000000"
@ -712,6 +794,11 @@ func (a *Authority) IsAdminAPIEnabled() bool {
// Shutdown safely shuts down any clients, databases, etc. held by the Authority.
func (a *Authority) Shutdown() error {
if a.crlTicker != nil {
a.crlTicker.Stop()
close(a.crlStopper)
}
if err := a.keyManager.Close(); err != nil {
log.Printf("error closing the key manager: %v", err)
}
@ -720,6 +807,11 @@ func (a *Authority) Shutdown() error {
// CloseForReload closes internal services, to allow a safe reload.
func (a *Authority) CloseForReload() {
if a.crlTicker != nil {
a.crlTicker.Stop()
close(a.crlStopper)
}
if err := a.keyManager.Close(); err != nil {
log.Printf("error closing the key manager: %v", err)
}
@ -760,11 +852,49 @@ func (a *Authority) requiresSCEPService() bool {
return false
}
// GetSCEPService returns the configured SCEP Service
// TODO: this function is intended to exist temporarily
// in order to make SCEP work more easily. It can be
// made more correct by using the right interfaces/abstractions
// after it works as expected.
// GetSCEPService returns the configured SCEP Service.
//
// TODO: this function is intended to exist temporarily in order to make SCEP
// work more easily. It can be made more correct by using the right
// interfaces/abstractions after it works as expected.
func (a *Authority) GetSCEPService() *scep.Service {
return a.scepService
}
func (a *Authority) startCRLGenerator() error {
if !a.config.CRL.IsEnabled() {
return nil
}
// Check that there is a valid CRL in the DB right now. If it doesn't exist
// or is expired, generate one now
_, ok := a.db.(db.CertificateRevocationListDB)
if !ok {
return errors.Errorf("CRL Generation requested, but database does not support CRL generation")
}
// Always create a new CRL on startup in case the CA has been down and the
// time to next expected CRL update is less than the cache duration.
if err := a.GenerateCertificateRevocationList(); err != nil {
return errors.Wrap(err, "could not generate a CRL")
}
a.crlStopper = make(chan struct{}, 1)
a.crlTicker = time.NewTicker(a.config.CRL.TickerDuration())
go func() {
for {
select {
case <-a.crlTicker.C:
log.Println("Regenerating CRL")
if err := a.GenerateCertificateRevocationList(); err != nil {
log.Printf("error regenerating the CRL: %v", err)
}
case <-a.crlStopper:
return
}
}
}()
return nil
}

@ -35,8 +35,13 @@ var (
// DefaultEnableSSHCA enable SSH CA features per provisioner or globally
// for all provisioners.
DefaultEnableSSHCA = false
// GlobalProvisionerClaims default claims for the Authority. Can be overridden
// by provisioner specific claims.
// DefaultCRLCacheDuration is the default cache duration for the CRL.
DefaultCRLCacheDuration = &provisioner.Duration{Duration: 24 * time.Hour}
// DefaultCRLExpiredDuration is the default duration in which expired
// certificates will remain in the CRL after expiration.
DefaultCRLExpiredDuration = time.Hour
// GlobalProvisionerClaims is the default duration that expired certificates
// remain in the CRL after expiration.
GlobalProvisionerClaims = provisioner.Claims{
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, // TLS certs
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
@ -72,7 +77,60 @@ type Config struct {
Password string `json:"password,omitempty"`
Templates *templates.Templates `json:"templates,omitempty"`
CommonName string `json:"commonName,omitempty"`
CRL *CRLConfig `json:"crl,omitempty"`
SkipValidation bool `json:"-"`
// Keeps record of the filename the Config is read from
loadedFromFilepath string
}
// CRLConfig represents config options for CRL generation
type CRLConfig struct {
Enabled bool `json:"enabled"`
GenerateOnRevoke bool `json:"generateOnRevoke,omitempty"`
CacheDuration *provisioner.Duration `json:"cacheDuration,omitempty"`
RenewPeriod *provisioner.Duration `json:"renewPeriod,omitempty"`
}
// IsEnabled returns if the CRL is enabled.
func (c *CRLConfig) IsEnabled() bool {
return c != nil && c.Enabled
}
// Validate validates the CRL configuration.
func (c *CRLConfig) Validate() error {
if c == nil {
return nil
}
if c.CacheDuration != nil && c.CacheDuration.Duration < 0 {
return errors.New("crl.cacheDuration must be greater than or equal to 0")
}
if c.RenewPeriod != nil && c.RenewPeriod.Duration < 0 {
return errors.New("crl.renewPeriod must be greater than or equal to 0")
}
if c.RenewPeriod != nil && c.CacheDuration != nil &&
c.RenewPeriod.Duration > c.CacheDuration.Duration {
return errors.New("crl.cacheDuration must be greater than or equal to crl.renewPeriod")
}
return nil
}
// TickerDuration the renewal ticker duration. This is set by renewPeriod, of it
// is not set is ~2/3 of cacheDuration.
func (c *CRLConfig) TickerDuration() time.Duration {
if !c.IsEnabled() {
return 0
}
if c.RenewPeriod != nil && c.RenewPeriod.Duration > 0 {
return c.RenewPeriod.Duration
}
return (c.CacheDuration.Duration / 3) * 2
}
// ASN1DN contains ASN1.DN attributes that are used in Subject and Issuer
@ -163,6 +221,10 @@ func LoadConfiguration(filename string) (*Config, error) {
return nil, errors.Wrapf(err, "error parsing %s", filename)
}
// store filename that was read to populate Config
c.loadedFromFilepath = filename
// initialize the Config
c.Init()
return &c, nil
@ -183,6 +245,9 @@ func (c *Config) Init() {
if c.CommonName == "" {
c.CommonName = "Step Online CA"
}
if c.CRL != nil && c.CRL.Enabled && c.CRL.CacheDuration == nil {
c.CRL.CacheDuration = DefaultCRLCacheDuration
}
c.AuthorityConfig.init()
}
@ -199,6 +264,30 @@ func (c *Config) Save(filename string) error {
return errors.Wrapf(enc.Encode(c), "error writing %s", filename)
}
// Commit saves the current configuration to the same
// file it was initially loaded from.
//
// TODO(hs): rename Save() to WriteTo() and replace this
// with Save()? Or is Commit clear enough.
func (c *Config) Commit() error {
if !c.WasLoadedFromFile() {
return errors.New("cannot commit configuration if not loaded from file")
}
return c.Save(c.loadedFromFilepath)
}
// WasLoadedFromFile returns whether or not the Config was
// loaded from a file.
func (c *Config) WasLoadedFromFile() bool {
return c.loadedFromFilepath != ""
}
// Filepath returns the path to the file the Config was
// loaded from.
func (c *Config) Filepath() string {
return c.loadedFromFilepath
}
// Validate validates the configuration.
func (c *Config) Validate() error {
switch {
@ -269,6 +358,11 @@ func (c *Config) Validate() error {
return err
}
// Validate crl config: nil is ok
if err := c.CRL.Validate(); err != nil {
return err
}
return c.AuthorityConfig.Validate(c.GetAudiences())
}

@ -86,6 +86,14 @@ func WithDatabase(d db.AuthDB) Option {
}
}
// WithQuietInit disables log output when the authority is initialized.
func WithQuietInit() Option {
return func(a *Authority) error {
a.quietInit = true
return nil
}
}
// WithWebhookClient sets the http.Client to be used for outbound requests.
func WithWebhookClient(c *http.Client) Option {
return func(a *Authority) error {

@ -5,11 +5,13 @@ import (
"crypto"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"math/big"
"net"
"net/http"
"strings"
@ -29,6 +31,7 @@ import (
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/webhook"
"github.com/smallstep/nosql/database"
)
// GetTLSOptions returns the tls options configured.
@ -36,8 +39,11 @@ func (a *Authority) GetTLSOptions() *config.TLSOptions {
return a.config.TLS
}
var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14}
var (
oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14}
oidExtensionIssuingDistributionPoint = asn1.ObjectIdentifier{2, 5, 29, 28}
)
func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
return func(crt *x509.Certificate, opts provisioner.SignOptions) error {
@ -320,7 +326,7 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
// Create new certificate from previous values.
// Issuer, NotBefore, NotAfter and SubjectKeyId will be set by the CAS.
newCert := &x509.Certificate{
Subject: oldCert.Subject,
RawSubject: oldCert.RawSubject,
KeyUsage: oldCert.KeyUsage,
UnhandledCriticalExtensions: oldCert.UnhandledCriticalExtensions,
ExtKeyUsage: oldCert.ExtKeyUsage,
@ -512,16 +518,23 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
RevokedAt: time.Now().UTC(),
}
var (
p provisioner.Interface
err error
)
// For X509 CRLs attempt to get the expiration date of the certificate.
if provisioner.MethodFromContext(ctx) == provisioner.RevokeMethod {
if revokeOpts.Crt == nil {
cert, err := a.db.GetCertificate(revokeOpts.Serial)
if err == nil {
rci.ExpiresAt = cert.NotAfter
}
} else {
rci.ExpiresAt = revokeOpts.Crt.NotAfter
}
}
// If not mTLS nor ACME, then get the TokenID of the token.
if !(revokeOpts.MTLS || revokeOpts.ACME) {
token, err := jose.ParseSigned(revokeOpts.OTT)
if err != nil {
return errs.Wrap(http.StatusUnauthorized, err,
"authority.Revoke; error parsing token", opts...)
return errs.Wrap(http.StatusUnauthorized, err, "authority.Revoke; error parsing token", opts...)
}
// Get claims w/out verification.
@ -531,28 +544,43 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
}
// This method will also validate the audiences for JWK provisioners.
p, err = a.LoadProvisionerByToken(token, &claims.Claims)
p, err := a.LoadProvisionerByToken(token, &claims.Claims)
if err != nil {
return err
}
rci.ProvisionerID = p.GetID()
rci.TokenID, err = p.GetTokenID(revokeOpts.OTT)
if err != nil && !errors.Is(err, provisioner.ErrAllowTokenReuse) {
return errs.Wrap(http.StatusInternalServerError, err,
"authority.Revoke; could not get ID for token")
return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke; could not get ID for token")
}
opts = append(opts,
errs.WithKeyVal("provisionerID", rci.ProvisionerID),
errs.WithKeyVal("tokenID", rci.TokenID),
)
} else if p, err = a.LoadProvisionerByCertificate(revokeOpts.Crt); err == nil {
} else if p, err := a.LoadProvisionerByCertificate(revokeOpts.Crt); err == nil {
// Load the Certificate provisioner if one exists.
rci.ProvisionerID = p.GetID()
opts = append(opts, errs.WithKeyVal("provisionerID", rci.ProvisionerID))
}
failRevoke := func(err error) error {
switch {
case errors.Is(err, db.ErrNotImplemented):
return errs.NotImplemented("authority.Revoke; no persistence layer configured", opts...)
case errors.Is(err, db.ErrAlreadyExists):
return errs.ApplyOptions(
errs.BadRequest("certificate with serial number '%s' is already revoked", rci.Serial),
opts...,
)
default:
return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...)
}
}
if provisioner.MethodFromContext(ctx) == provisioner.SSHRevokeMethod {
err = a.revokeSSH(nil, rci)
if err := a.revokeSSH(nil, rci); err != nil {
return failRevoke(err)
}
} else {
// Revoke an X.509 certificate using CAS. If the certificate is not
// provided we will try to read it from the db. If the read fails we
@ -567,7 +595,7 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
// CAS operation, note that SoftCAS (default) is a noop.
// The revoke happens when this is stored in the db.
_, err = a.x509CAService.RevokeCertificate(&casapi.RevokeCertificateRequest{
_, err := a.x509CAService.RevokeCertificate(&casapi.RevokeCertificateRequest{
Certificate: revokedCert,
SerialNumber: rci.Serial,
Reason: rci.Reason,
@ -579,21 +607,20 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
}
// Save as revoked in the Db.
err = a.revoke(revokedCert, rci)
}
switch {
case err == nil:
return nil
case errors.Is(err, db.ErrNotImplemented):
return errs.NotImplemented("authority.Revoke; no persistence layer configured", opts...)
case errors.Is(err, db.ErrAlreadyExists):
return errs.ApplyOptions(
errs.BadRequest("certificate with serial number '%s' is already revoked", rci.Serial),
opts...,
)
default:
return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...)
if err := a.revoke(revokedCert, rci); err != nil {
return failRevoke(err)
}
// Generate a new CRL so CRL requesters will always get an up-to-date
// CRL whenever they request it.
if a.config.CRL.IsEnabled() && a.config.CRL.GenerateOnRevoke {
if err := a.GenerateCertificateRevocationList(); err != nil {
return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...)
}
}
}
return nil
}
func (a *Authority) revoke(crt *x509.Certificate, rci *db.RevokedCertificateInfo) error {
@ -614,6 +641,137 @@ func (a *Authority) revokeSSH(crt *ssh.Certificate, rci *db.RevokedCertificateIn
return a.db.RevokeSSH(rci)
}
// GetCertificateRevocationList will return the currently generated CRL from the DB, or a not implemented
// error if the underlying AuthDB does not support CRLs
func (a *Authority) GetCertificateRevocationList() ([]byte, error) {
if !a.config.CRL.IsEnabled() {
return nil, errs.Wrap(http.StatusNotFound, errors.Errorf("Certificate Revocation Lists are not enabled"), "authority.GetCertificateRevocationList")
}
crlDB, ok := a.db.(db.CertificateRevocationListDB)
if !ok {
return nil, errs.Wrap(http.StatusNotImplemented, errors.Errorf("Database does not support Certificate Revocation Lists"), "authority.GetCertificateRevocationList")
}
crlInfo, err := crlDB.GetCRL()
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetCertificateRevocationList")
}
return crlInfo.DER, nil
}
// GenerateCertificateRevocationList generates a DER representation of a signed CRL and stores it in the
// database. Returns nil if CRL generation has been disabled in the config
func (a *Authority) GenerateCertificateRevocationList() error {
if !a.config.CRL.IsEnabled() {
return nil
}
crlDB, ok := a.db.(db.CertificateRevocationListDB)
if !ok {
return errors.Errorf("Database does not support CRL generation")
}
// some CAS may not implement the CRLGenerator interface, so check before we proceed
caCRLGenerator, ok := a.x509CAService.(casapi.CertificateAuthorityCRLGenerator)
if !ok {
return errors.Errorf("CA does not support CRL Generation")
}
// use a mutex to ensure only one CRL is generated at a time to avoid
// concurrency issues
a.crlMutex.Lock()
defer a.crlMutex.Unlock()
crlInfo, err := crlDB.GetCRL()
if err != nil && !database.IsErrNotFound(err) {
return errors.Wrap(err, "could not retrieve CRL from database")
}
now := time.Now().Truncate(time.Second).UTC()
revokedList, err := crlDB.GetRevokedCertificates()
if err != nil {
return errors.Wrap(err, "could not retrieve revoked certificates list from database")
}
// Number is a monotonically increasing integer (essentially the CRL version
// number) that we need to keep track of and increase every time we generate
// a new CRL
var bn big.Int
if crlInfo != nil {
bn.SetInt64(crlInfo.Number + 1)
}
// Convert our database db.RevokedCertificateInfo types into the pkix
// representation ready for the CAS to sign it
var revokedCertificates []pkix.RevokedCertificate
skipExpiredTime := now.Add(-config.DefaultCRLExpiredDuration)
for _, revokedCert := range *revokedList {
// skip expired certificates
if !revokedCert.ExpiresAt.IsZero() && revokedCert.ExpiresAt.Before(skipExpiredTime) {
continue
}
var sn big.Int
sn.SetString(revokedCert.Serial, 10)
revokedCertificates = append(revokedCertificates, pkix.RevokedCertificate{
SerialNumber: &sn,
RevocationTime: revokedCert.RevokedAt,
Extensions: nil,
})
}
var updateDuration time.Duration
if a.config.CRL.CacheDuration != nil {
updateDuration = a.config.CRL.CacheDuration.Duration
} else if crlInfo != nil {
updateDuration = crlInfo.Duration
}
// Create a RevocationList representation ready for the CAS to sign
// TODO: allow SignatureAlgorithm to be specified?
revocationList := x509.RevocationList{
SignatureAlgorithm: 0,
RevokedCertificates: revokedCertificates,
Number: &bn,
ThisUpdate: now,
NextUpdate: now.Add(updateDuration),
}
// 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},
}
}
certificateRevocationList, err := caCRLGenerator.CreateCRL(&casapi.CreateCRLRequest{RevocationList: &revocationList})
if err != nil {
return errors.Wrap(err, "could not create CRL")
}
// Create a new db.CertificateRevocationListInfo, which stores the new Number we just generated, the
// expiry time, duration, and the DER-encoded CRL
newCRLInfo := db.CertificateRevocationListInfo{
Number: bn.Int64(),
ExpiresAt: revocationList.NextUpdate,
DER: certificateRevocationList.CRL,
Duration: updateDuration,
}
// Store the CRL in the database ready for retrieval by api endpoints
err = crlDB.StoreCRL(&newCRLInfo)
if err != nil {
return errors.Wrap(err, "could not store CRL in database")
}
return nil
}
// GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server.
func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
fatal := func(err error) (*tls.Certificate, error) {
@ -707,6 +865,33 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
return &tlsCrt, nil
}
// RFC 5280, 5.2.5
type distributionPoint struct {
DistributionPoint distributionPointName `asn1:"optional,tag:0"`
OnlyContainsUserCerts bool `asn1:"optional,tag:1"`
OnlyContainsCACerts bool `asn1:"optional,tag:2"`
OnlySomeReasons asn1.BitString `asn1:"optional,tag:3"`
IndirectCRL bool `asn1:"optional,tag:4"`
OnlyContainsAttributeCerts bool `asn1:"optional,tag:5"`
}
type distributionPointName struct {
FullName []asn1.RawValue `asn1:"optional,tag:0"`
RelativeName pkix.RDNSequence `asn1:"optional,tag:1"`
}
func marshalDistributionPoint(fullName string, isCA bool) ([]byte, error) {
return asn1.Marshal(distributionPoint{
DistributionPoint: distributionPointName{
FullName: []asn1.RawValue{
{Class: 2, Tag: 6, Bytes: []byte(fullName)},
},
},
OnlyContainsUserCerts: !isCA,
OnlyContainsCACerts: isCA,
})
}
// templatingError tries to extract more information about the cause of
// an error related to (most probably) malformed template data and adds
// this to the error message.

@ -26,11 +26,13 @@ import (
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/cas/softcas"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs"
"github.com/smallstep/nosql/database"
)
var (
@ -139,6 +141,13 @@ func generateIntermidiateCertificate(t *testing.T, issuer *x509.Certificate, sig
return cert, priv
}
func withSubject(sub pkix.Name) provisioner.CertificateModifierFunc {
return func(crt *x509.Certificate, _ provisioner.SignOptions) error {
crt.Subject = sub
return nil
}
}
func withProvisionerOID(name, kid string) provisioner.CertificateModifierFunc {
return func(crt *x509.Certificate, _ provisioner.SignOptions) error {
b, err := asn1.Marshal(stepProvisionerASN1{
@ -952,6 +961,18 @@ func TestAuthority_Renew(t *testing.T) {
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID),
withSigner(issuer, signer))
certExtraNames := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
withSubject(pkix.Name{
CommonName: "renew",
ExtraNames: []pkix.AttributeTypeAndValue{
{Type: asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}, Value: "dc"},
},
}),
withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
withDefaultASN1DN(a.config.AuthorityConfig.Template),
withProvisionerOID("Max", a.config.AuthorityConfig.Provisioners[0].(*provisioner.JWK).Key.KeyID),
withSigner(issuer, signer))
certNoRenew := generateCertificate(t, "renew", []string{"test.smallstep.com", "test"},
withNotBeforeNotAfter(so.NotBefore.Time(), so.NotAfter.Time()),
withDefaultASN1DN(a.config.AuthorityConfig.Template),
@ -1001,6 +1022,12 @@ func TestAuthority_Renew(t *testing.T) {
cert: cert,
}, nil
},
"ok/WithExtraNames": func() (*renewTest, error) {
return &renewTest{
auth: a,
cert: certExtraNames,
}, nil
},
"ok/success-new-intermediate": func() (*renewTest, error) {
rootCert, rootSigner := generateRootCertificate(t)
intCert, intSigner := generateIntermidiateCertificate(t, rootCert, rootSigner)
@ -1063,15 +1090,14 @@ func TestAuthority_Renew(t *testing.T) {
assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour)))
tmplt := a.config.AuthorityConfig.Template
assert.Equals(t, leaf.Subject.String(),
pkix.Name{
Country: []string{tmplt.Country},
Organization: []string{tmplt.Organization},
Locality: []string{tmplt.Locality},
StreetAddress: []string{tmplt.StreetAddress},
Province: []string{tmplt.Province},
CommonName: tmplt.CommonName,
}.String())
assert.Equals(t, leaf.RawSubject, tc.cert.RawSubject)
assert.Equals(t, leaf.Subject.Country, []string{tmplt.Country})
assert.Equals(t, leaf.Subject.Organization, []string{tmplt.Organization})
assert.Equals(t, leaf.Subject.Locality, []string{tmplt.Locality})
assert.Equals(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress})
assert.Equals(t, leaf.Subject.Province, []string{tmplt.Province})
assert.Equals(t, leaf.Subject.CommonName, tmplt.CommonName)
assert.Equals(t, leaf.Issuer, intermediate.Subject)
assert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256)
@ -1474,7 +1500,7 @@ func TestAuthority_Revoke(t *testing.T) {
return true, nil
},
MGetCertificate: func(sn string) (*x509.Certificate, error) {
return nil, nil
return nil, errors.New("not found")
},
Err: errors.New("force"),
}))
@ -1514,7 +1540,7 @@ func TestAuthority_Revoke(t *testing.T) {
return true, nil
},
MGetCertificate: func(sn string) (*x509.Certificate, error) {
return nil, nil
return nil, errors.New("not found")
},
Err: db.ErrAlreadyExists,
}))
@ -1783,3 +1809,148 @@ func TestAuthority_constraints(t *testing.T) {
})
}
}
func TestAuthority_CRL(t *testing.T) {
reasonCode := 2
reason := "bob was let go"
validIssuer := "step-cli"
validAudience := testAudiences.Revoke
now := time.Now().UTC()
//
jwk, err := jose.ReadKey("testdata/secrets/step_cli_key_priv.jwk", jose.WithPassword([]byte("pass")))
assert.FatalError(t, err)
//
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key},
(&jose.SignerOptions{}).WithType("JWT").WithHeader("kid", jwk.KeyID))
assert.FatalError(t, err)
crlCtx := provisioner.NewContextWithMethod(context.Background(), provisioner.RevokeMethod)
var crlStore db.CertificateRevocationListInfo
var revokedList []db.RevokedCertificateInfo
type test struct {
auth *Authority
ctx context.Context
expected []string
err error
}
tests := map[string]func() test{
"fail/empty-crl": func() test {
a := testAuthority(t, WithDatabase(&db.MockAuthDB{
MUseToken: func(id, tok string) (bool, error) {
return true, nil
},
MGetCertificate: func(sn string) (*x509.Certificate, error) {
return nil, errors.New("not found")
},
MStoreCRL: func(i *db.CertificateRevocationListInfo) error {
crlStore = *i
return nil
},
MGetCRL: func() (*db.CertificateRevocationListInfo, error) {
return nil, database.ErrNotFound
},
MGetRevokedCertificates: func() (*[]db.RevokedCertificateInfo, error) {
return &revokedList, nil
},
MRevoke: func(rci *db.RevokedCertificateInfo) error {
revokedList = append(revokedList, *rci)
return nil
},
}))
a.config.CRL = &config.CRLConfig{
Enabled: true,
}
return test{
auth: a,
ctx: crlCtx,
expected: nil,
err: database.ErrNotFound,
}
},
"ok/crl-full": func() test {
a := testAuthority(t, WithDatabase(&db.MockAuthDB{
MUseToken: func(id, tok string) (bool, error) {
return true, nil
},
MGetCertificate: func(sn string) (*x509.Certificate, error) {
return nil, errors.New("not found")
},
MStoreCRL: func(i *db.CertificateRevocationListInfo) error {
crlStore = *i
return nil
},
MGetCRL: func() (*db.CertificateRevocationListInfo, error) {
return &crlStore, nil
},
MGetRevokedCertificates: func() (*[]db.RevokedCertificateInfo, error) {
return &revokedList, nil
},
MRevoke: func(rci *db.RevokedCertificateInfo) error {
revokedList = append(revokedList, *rci)
return nil
},
}))
a.config.CRL = &config.CRLConfig{
Enabled: true,
GenerateOnRevoke: true,
}
var ex []string
for i := 0; i < 100; i++ {
sn := fmt.Sprintf("%v", i)
cl := jose.Claims{
Subject: fmt.Sprintf("sn-%v", i),
Issuer: validIssuer,
NotBefore: jose.NewNumericDate(now),
Expiry: jose.NewNumericDate(now.Add(time.Minute)),
Audience: validAudience,
ID: sn,
}
raw, err := jose.Signed(sig).Claims(cl).CompactSerialize()
assert.FatalError(t, err)
err = a.Revoke(crlCtx, &RevokeOptions{
Serial: sn,
ReasonCode: reasonCode,
Reason: reason,
OTT: raw,
})
assert.FatalError(t, err)
ex = append(ex, sn)
}
return test{
auth: a,
ctx: crlCtx,
expected: ex,
}
},
}
for name, f := range tests {
tc := f()
t.Run(name, func(t *testing.T) {
if crlBytes, err := tc.auth.GetCertificateRevocationList(); err == nil {
crl, parseErr := x509.ParseCRL(crlBytes)
if parseErr != nil {
t.Errorf("x509.ParseCertificateRequest() error = %v, wantErr %v", parseErr, nil)
return
}
var cmpList []string
for _, c := range crl.TBSCertList.RevokedCertificates {
cmpList = append(cmpList, c.SerialNumber.String())
}
assert.Equals(t, cmpList, tc.expected)
} else {
assert.NotNil(t, tc.err, err.Error())
}
})
}
}

@ -156,6 +156,10 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
opts = append(opts, authority.WithDatabase(ca.opts.database))
}
if ca.opts.quiet {
opts = append(opts, authority.WithQuietInit())
}
webhookTransport := http.DefaultTransport.(*http.Transport).Clone()
opts = append(opts, authority.WithWebhookClient(&http.Client{Transport: webhookTransport}))
@ -345,7 +349,7 @@ func (ca *CA) Run() error {
if step.Contexts().GetCurrent() != nil {
log.Printf("Current context: %s", step.Contexts().GetCurrent().Name)
}
log.Printf("Config file: %s", ca.opts.configFile)
log.Printf("Config file: %s", ca.getConfigFileOutput())
baseURL := fmt.Sprintf("https://%s%s",
authorityInfo.DNSNames[0],
ca.config.Address[strings.LastIndex(ca.config.Address, ":"):])
@ -565,3 +569,10 @@ func dumpRoutes(mux chi.Routes) {
fmt.Printf("Logging err: %s\n", err.Error())
}
}
func (ca *CA) getConfigFileOutput() string {
if ca.config.WasLoadedFromFile() {
return ca.config.Filepath()
}
return "loaded from token"
}

@ -154,3 +154,13 @@ type CreateCertificateAuthorityResponse struct {
PrivateKey crypto.PrivateKey
Signer crypto.Signer
}
// CreateCRLRequest is the request to create a Certificate Revocation List.
type CreateCRLRequest struct {
RevocationList *x509.RevocationList
}
// CreateCRLResponse is the response to a Certificate Revocation List request.
type CreateCRLResponse struct {
CRL []byte //the CRL in DER format
}

@ -14,6 +14,12 @@ type CertificateAuthorityService interface {
RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error)
}
// CertificateAuthorityCRLGenerator is an optional interface implemented by CertificateAuthorityService
// that has a method to create a CRL
type CertificateAuthorityCRLGenerator interface {
CreateCRL(req *CreateCRLRequest) (*CreateCRLResponse, error)
}
// CertificateAuthorityGetter is an interface implemented by a
// CertificateAuthorityService that has a method to get the root certificate.
type CertificateAuthorityGetter interface {

@ -3,6 +3,7 @@ package softcas
import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"time"
@ -132,6 +133,20 @@ func (c *SoftCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1
}, nil
}
// CreateCRL will create a new CRL based on the RevocationList passed to it
func (c *SoftCAS) CreateCRL(req *apiv1.CreateCRLRequest) (*apiv1.CreateCRLResponse, error) {
certChain, signer, err := c.getCertSigner()
if err != nil {
return nil, err
}
revocationListBytes, err := x509.CreateRevocationList(rand.Reader, req.RevocationList, certChain[0], signer)
if err != nil {
return nil, err
}
return &apiv1.CreateCRLResponse{CRL: revocationListBytes}, nil
}
// CreateCertificateAuthority creates a root or an intermediate certificate.
func (c *SoftCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthorityRequest) (*apiv1.CreateCertificateAuthorityResponse, error) {
switch {

@ -12,6 +12,7 @@ import (
"unicode"
"github.com/pkg/errors"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/ca"
@ -68,9 +69,23 @@ certificate issuer private key used in the RA mode.`,
},
cli.StringFlag{
Name: "context",
Usage: "The name of the authority's context.",
Usage: "the <name> of the authority's context.",
EnvVar: "STEP_CA_CONTEXT",
},
cli.IntFlag{
Name: "acme-http-port",
Usage: `the <port> used on http-01 challenges. It can be changed for testing purposes.
Requires **--insecure** flag.`,
},
cli.IntFlag{
Name: "acme-tls-port",
Usage: `the <port> used on tls-alpn-01 challenges. It can be changed for testing purposes.
Requires **--insecure** flag.`,
},
cli.BoolFlag{
Name: "insecure",
Usage: "enable insecure flags.",
},
},
}
@ -88,6 +103,23 @@ func appAction(ctx *cli.Context) error {
return errs.TooManyArguments(ctx)
}
// Allow custom ACME ports with insecure
if acmePort := ctx.Int("acme-http-port"); acmePort != 0 {
if ctx.Bool("insecure") {
acme.InsecurePortHTTP01 = acmePort
} else {
return fmt.Errorf("flag '--acme-http-port' requires the '--insecure' flag")
}
}
if acmePort := ctx.Int("acme-tls-port"); acmePort != 0 {
if ctx.Bool("insecure") {
acme.InsecurePortTLSALPN01 = acmePort
} else {
return fmt.Errorf("flag '--acme-tls-port' requires the '--insecure' flag")
}
}
// Allow custom contexts.
if caCtx := ctx.String("context"); caCtx != "" {
if err := step.Contexts().SetCurrent(caCtx); err != nil {
return err

@ -19,6 +19,7 @@ var (
certsTable = []byte("x509_certs")
certsDataTable = []byte("x509_certs_data")
revokedCertsTable = []byte("revoked_x509_certs")
crlTable = []byte("x509_crl")
revokedSSHCertsTable = []byte("revoked_ssh_certs")
usedOTTTable = []byte("used_ott")
sshCertsTable = []byte("ssh_certs")
@ -27,6 +28,9 @@ var (
sshHostPrincipalsTable = []byte("ssh_host_principals")
)
var crlKey = []byte("crl") //TODO: at the moment we store a single CRL in the database, in a dedicated table.
// is this acceptable? probably not....
// ErrAlreadyExists can be returned if the DB attempts to set a key that has
// been previously set.
var ErrAlreadyExists = errors.New("already exists")
@ -87,6 +91,13 @@ type CertificateStorer interface {
StoreSSHCertificate(crt *ssh.Certificate) error
}
// CertificateRevocationListDB is an interface to indicate whether the DB supports CRL generation
type CertificateRevocationListDB interface {
GetRevokedCertificates() (*[]RevokedCertificateInfo, error)
GetCRL() (*CertificateRevocationListInfo, error)
StoreCRL(*CertificateRevocationListInfo) error
}
// DB is a wrapper over the nosql.DB interface.
type DB struct {
nosql.DB
@ -113,7 +124,7 @@ func New(c *Config) (AuthDB, error) {
tables := [][]byte{
revokedCertsTable, certsTable, usedOTTTable,
sshCertsTable, sshHostsTable, sshHostPrincipalsTable, sshUsersTable,
revokedSSHCertsTable, certsDataTable,
revokedSSHCertsTable, certsDataTable, crlTable,
}
for _, b := range tables {
if err := db.CreateTable(b); err != nil {
@ -133,11 +144,21 @@ type RevokedCertificateInfo struct {
ReasonCode int
Reason string
RevokedAt time.Time
ExpiresAt time.Time
TokenID string
MTLS bool
ACME bool
}
// CertificateRevocationListInfo contains a CRL in DER format and associated
// metadata to allow a decision on whether to regenerate the CRL or not easier
type CertificateRevocationListInfo struct {
Number int64
ExpiresAt time.Time
Duration time.Duration
DER []byte
}
// IsRevoked returns whether or not a certificate with the given identifier
// has been revoked.
// In the case of an X509 Certificate the `id` should be the Serial Number of
@ -220,6 +241,51 @@ func (db *DB) RevokeSSH(rci *RevokedCertificateInfo) error {
}
}
// GetRevokedCertificates gets a list of all revoked certificates.
func (db *DB) GetRevokedCertificates() (*[]RevokedCertificateInfo, error) {
entries, err := db.List(revokedCertsTable)
if err != nil {
return nil, err
}
var revokedCerts []RevokedCertificateInfo
for _, e := range entries {
var data RevokedCertificateInfo
if err := json.Unmarshal(e.Value, &data); err != nil {
return nil, err
}
revokedCerts = append(revokedCerts, data)
}
return &revokedCerts, nil
}
// StoreCRL stores a CRL in the DB
func (db *DB) StoreCRL(crlInfo *CertificateRevocationListInfo) error {
crlInfoBytes, err := json.Marshal(crlInfo)
if err != nil {
return errors.Wrap(err, "json Marshal error")
}
if err := db.Set(crlTable, crlKey, crlInfoBytes); err != nil {
return errors.Wrap(err, "database Set error")
}
return nil
}
// GetCRL gets the existing CRL from the database
func (db *DB) GetCRL() (*CertificateRevocationListInfo, error) {
crlInfoBytes, err := db.Get(crlTable, crlKey)
if err != nil {
return nil, errors.Wrap(err, "database Get error")
}
var crlInfo CertificateRevocationListInfo
err = json.Unmarshal(crlInfoBytes, &crlInfo)
if err != nil {
return nil, errors.Wrap(err, "json Unmarshal error")
}
return &crlInfo, err
}
// GetCertificate retrieves a certificate by the serial number.
func (db *DB) GetCertificate(serialNumber string) (*x509.Certificate, error) {
asn1Data, err := db.Get(certsTable, []byte(serialNumber))
@ -382,20 +448,44 @@ func (db *DB) Shutdown() error {
// MockAuthDB mocks the AuthDB interface. //
type MockAuthDB struct {
Err error
Ret1 interface{}
MIsRevoked func(string) (bool, error)
MIsSSHRevoked func(string) (bool, error)
MRevoke func(rci *RevokedCertificateInfo) error
MRevokeSSH func(rci *RevokedCertificateInfo) error
MGetCertificate func(serialNumber string) (*x509.Certificate, error)
MGetCertificateData func(serialNumber string) (*CertificateData, error)
MStoreCertificate func(crt *x509.Certificate) error
MUseToken func(id, tok string) (bool, error)
MIsSSHHost func(principal string) (bool, error)
MStoreSSHCertificate func(crt *ssh.Certificate) error
MGetSSHHostPrincipals func() ([]string, error)
MShutdown func() error
Err error
Ret1 interface{}
MIsRevoked func(string) (bool, error)
MIsSSHRevoked func(string) (bool, error)
MRevoke func(rci *RevokedCertificateInfo) error
MRevokeSSH func(rci *RevokedCertificateInfo) error
MGetCertificate func(serialNumber string) (*x509.Certificate, error)
MGetCertificateData func(serialNumber string) (*CertificateData, error)
MStoreCertificate func(crt *x509.Certificate) error
MUseToken func(id, tok string) (bool, error)
MIsSSHHost func(principal string) (bool, error)
MStoreSSHCertificate func(crt *ssh.Certificate) error
MGetSSHHostPrincipals func() ([]string, error)
MShutdown func() error
MGetRevokedCertificates func() (*[]RevokedCertificateInfo, error)
MGetCRL func() (*CertificateRevocationListInfo, error)
MStoreCRL func(*CertificateRevocationListInfo) error
}
func (m *MockAuthDB) GetRevokedCertificates() (*[]RevokedCertificateInfo, error) {
if m.MGetRevokedCertificates != nil {
return m.MGetRevokedCertificates()
}
return m.Ret1.(*[]RevokedCertificateInfo), m.Err
}
func (m *MockAuthDB) GetCRL() (*CertificateRevocationListInfo, error) {
if m.MGetCRL != nil {
return m.MGetCRL()
}
return m.Ret1.(*CertificateRevocationListInfo), m.Err
}
func (m *MockAuthDB) StoreCRL(info *CertificateRevocationListInfo) error {
if m.MStoreCRL != nil {
return m.MStoreCRL(info)
}
return m.Err
}
// IsRevoked mock.

@ -41,6 +41,21 @@ func (s *SimpleDB) Revoke(rci *RevokedCertificateInfo) error {
return ErrNotImplemented
}
// GetRevokedCertificates returns a "NotImplemented" error.
func (s *SimpleDB) GetRevokedCertificates() (*[]RevokedCertificateInfo, error) {
return nil, ErrNotImplemented
}
// GetCRL returns a "NotImplemented" error.
func (s *SimpleDB) GetCRL() (*CertificateRevocationListInfo, error) {
return nil, ErrNotImplemented
}
// StoreCRL returns a "NotImplemented" error.
func (s *SimpleDB) StoreCRL(crlInfo *CertificateRevocationListInfo) error {
return ErrNotImplemented
}
// RevokeSSH returns a "NotImplemented" error.
func (s *SimpleDB) RevokeSSH(rci *RevokedCertificateInfo) error {
return ErrNotImplemented

@ -4,6 +4,7 @@ WORKDIR /src
COPY . .
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

@ -5,6 +5,7 @@ COPY . .
RUN apk add --no-cache curl git make
RUN apk add --no-cache gcc musl-dev pkgconf pcsc-lite-dev
RUN make V=1 download
RUN make V=1 GOFLAGS="" build

@ -126,17 +126,20 @@ 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).
* `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.

@ -3,15 +3,16 @@ module github.com/smallstep/certificates
go 1.18
require (
cloud.google.com/go v0.104.0
cloud.google.com/go/security v1.8.0
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
cloud.google.com/go v0.105.0 // indirect
cloud.google.com/go/longrunning v0.2.1
cloud.google.com/go/security v1.9.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/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/aws/aws-sdk-go v1.44.111 // indirect
github.com/aws/aws-sdk-go v1.44.117 // 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
@ -23,7 +24,7 @@ require (
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/googleapis/gax-go/v2 v2.6.0
github.com/hashicorp/vault/api v1.8.1
github.com/hashicorp/vault/api v1.8.2
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
@ -38,18 +39,18 @@ require (
github.com/slackhq/nebula v1.6.1
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
github.com/smallstep/nosql v0.5.0
github.com/stretchr/testify v1.8.0
github.com/stretchr/testify v1.8.1
github.com/urfave/cli v1.22.10
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
go.step.sm/cli-utils v0.7.5
go.step.sm/crypto v0.21.0
go.step.sm/crypto v0.22.0
go.step.sm/linkedca v0.19.0-rc.3
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
google.golang.org/api v0.99.0
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e
google.golang.org/api v0.101.0
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e
google.golang.org/grpc v1.50.1
google.golang.org/protobuf v1.28.1
gopkg.in/square/go-jose.v2 v2.6.0
@ -95,7 +96,7 @@ require (
github.com/hashicorp/go-hclog v0.16.2 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.4.3 // indirect
github.com/hashicorp/go-plugin v1.4.5 // indirect
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect
@ -141,8 +142,8 @@ require (
go.etcd.io/bbolt v1.3.6 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 // indirect
golang.org/x/text v0.3.8 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/text v0.4.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
@ -155,4 +156,4 @@ require (
// replace go.step.sm/linkedca => ../linkedca
// use github.com/smallstep/pkcs7 fork with patches applied
replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20211016004704-52592125d6f6
replace go.mozilla.org/pkcs7 => github.com/smallstep/pkcs7 v0.0.0-20221024180420-e1aab68dda05

@ -28,8 +28,8 @@ cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Ud
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.104.0 h1:gSmWO7DY1vOm0MVU6DNXM11BWHHsTUmsC5cv1fuW5X8=
cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
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/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -47,12 +47,14 @@ cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/kms v1.4.0 h1:iElbfoE61VeLhnZcGOltqL8HIly8Nhbe5t6JlH9GXjo=
cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=
cloud.google.com/go/longrunning v0.2.1 h1:x3E/YapFCMe2G1D9qCv9COrBldOwK/n0OC7w9PLzeX0=
cloud.google.com/go/longrunning v0.2.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/security v1.8.0 h1:linnRc3/gJYDfKbAtNixVQ52+66DpOx5MmCz0NNxal8=
cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
cloud.google.com/go/security v1.9.0 h1:o9frPOtXW2f4zMlw4SYPE42LRz/hhrYVWtAEUkPvyA4=
cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
@ -63,8 +65,8 @@ 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=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw=
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v67.0.0+incompatible h1:SVBwznSETB0Sipd0uyGJr7khLhJOFRUEUb+0JgkCvDo=
github.com/Azure/azure-sdk-for-go v67.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
@ -128,8 +130,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.111 h1:AcWfOgeedSQ4gQVwcIe6aLxpQNJMloZQyqnr7Dzki+s=
github.com/aws/aws-sdk-go v1.44.111/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.117 h1:mZuODB3Y4soG9QWAXyGb2po+6Easa/enifpj4MnZ91s=
github.com/aws/aws-sdk-go v1.44.117/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
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=
@ -393,8 +395,9 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo=
github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
@ -434,8 +437,8 @@ 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.1 h1:bMieWIe6dAlqAAPReZO/8zYtXaWUg/21umwqGZpEjCI=
github.com/hashicorp/vault/api v1.8.1/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/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=
@ -710,8 +713,8 @@ github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
github.com/smallstep/nosql v0.5.0 h1:1BPyHy8bha8qSaxgULGEdqhXpNFXimAfudnauFVqmxw=
github.com/smallstep/nosql v0.5.0/go.mod h1:yKZT5h7cdIVm6wEKM9+jN5dgK80Hljpuy8HNsnI7Gzo=
github.com/smallstep/pkcs7 v0.0.0-20211016004704-52592125d6f6 h1:8Rjy6IZbSM/jcYgBWCoLIGjug7QcoLtF9sUuhDrHD2U=
github.com/smallstep/pkcs7 v0.0.0-20211016004704-52592125d6f6/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
github.com/smallstep/pkcs7 v0.0.0-20221024180420-e1aab68dda05 h1:nVZXaJTwrUcfPUSZknkOidfITqOXSO0wE8pkOUTOdSM=
github.com/smallstep/pkcs7 v0.0.0-20221024180420-e1aab68dda05/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@ -736,8 +739,9 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -745,8 +749,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@ -786,8 +791,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
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.21.0 h1:Qwxk5JrqG0Q1t8tOfDM3zKTNECG6J5J24qgWZCRM0Ic=
go.step.sm/crypto v0.21.0/go.mod h1:diT2XWIHQy0397UI3i78qCKeLLLp2wu0/DIJI66u/MU=
go.step.sm/crypto v0.22.0 h1:aloAQsNYEX87e4xL+jqsd6zCA4mGThPBmz2akMKss8Q=
go.step.sm/crypto v0.22.0/go.mod h1:7PTDUApWUTmQbDt4x82cbk3nx5k7JwBo2dFG9CGEp9A=
go.step.sm/linkedca v0.19.0-rc.3 h1:3Uu8j187wm7mby+/pz/aQ0wHKRm7w/2AsVPpvcAn4v8=
go.step.sm/linkedca v0.19.0-rc.3/go.mod h1:MCZmPIdzElEofZbiw4eyUHayXgFTwa94cNAV34aJ5ew=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -906,8 +911,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 h1:MgJ6t2zo8v0tbmLCueaCbF1RM+TtB0rs3Lv8DGtOIpY=
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
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/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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -924,8 +929,8 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 h1:3VPzK7eqH25j7GYw5w6g/GzNRc0/fYtrxz27z1gD4W0=
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1025,8 +1030,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
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/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1135,8 +1140,8 @@ google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3h
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.99.0 h1:tsBtOIklCE2OFxhmcYSVqGwSAN/Y897srxmcvAQnwK8=
google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08=
google.golang.org/api v0.101.0 h1:lJPPeEBIRxGpGLwnBTam1NPEM8Z2BmmXEd3z812pjwM=
google.golang.org/api v0.101.0/go.mod h1:CjxAAWWt3A3VrUE2IGDY2bgK5qhoG/OkyWVlYcP05MY=
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=
@ -1212,8 +1217,8 @@ google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ6
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e h1:halCgTFuLWDRD61piiNSxPsARANGD3Xl16hPrLgLiIg=
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y=
google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
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=

@ -1,91 +0,0 @@
#########################################
# Building Docker Image
#
# This uses a multi-stage build file. The first stage is a builder (that might
# be large in size). After the build has succeeded, the statically linked
# binary is copied to a new image that is optimized for size.
#########################################
ifeq (, $(shell which docker))
DOCKER_CLIENT_OS := linux
else
DOCKER_CLIENT_OS := $(strip $(shell docker version -f '{{.Client.Os}}' 2>/dev/null))
endif
DOCKER_PLATFORMS = linux/amd64,linux/386,linux/arm,linux/arm64
DOCKER_IMAGE_NAME = smallstep/step-ca
docker-prepare:
# Ensure, we can build for ARM architecture
ifeq (linux,$(DOCKER_CLIENT_OS))
[ -f /proc/sys/fs/binfmt_misc/qemu-arm ] || docker run --rm --privileged linuxkit/binfmt:v0.8-amd64
endif
# Register buildx builder
mkdir -p $$HOME/.docker/cli-plugins
test -f $$HOME/.docker/cli-plugins/docker-buildx || \
(wget -q -O $$HOME/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.$(DOCKER_CLIENT_OS)-amd64 && \
chmod +x $$HOME/.docker/cli-plugins/docker-buildx)
docker buildx create --use --name mybuilder --platform="$(DOCKER_PLATFORMS)" || true
.PHONY: docker-prepare
#################################################
# Releasing Docker Images
#
# Using the docker build infrastructure, this section is responsible for
# logging into docker hub.
#################################################
# Rely on DOCKER_USERNAME and DOCKER_PASSWORD being set inside the CI or
# equivalent environment
docker-login:
$Q docker login -u="$(DOCKER_USERNAME)" -p="$(DOCKER_PASSWORD)"
.PHONY: docker-login
#################################################
# Targets for different type of builds
#################################################
define DOCKER_BUILDX
# $(1) -- Image Tag
# $(2) -- Push (empty is no push | --push will push to dockerhub)
docker buildx build . --progress plain -t $(DOCKER_IMAGE_NAME):$(1) -f docker/Dockerfile.step-ca --platform="$(DOCKER_PLATFORMS)" $(2)
echo -n "$(COSIGN_PWD)" | cosign sign -key /tmp/cosign.key -r $(DOCKER_IMAGE_NAME):$(1)
endef
# For non-master builds don't build the docker containers.
docker-branch:
# For master builds don't build the docker containers.
docker-master:
# For all builds with a release candidate tag build and push the containers.
docker-release-candidate: docker-prepare docker-login
$(call DOCKER_BUILDX,$(VERSION),--push)
# For all builds with a release tag build and push the containers.
docker-release: docker-prepare docker-login
$(call DOCKER_BUILDX,latest,--push)
$(call DOCKER_BUILDX,$(VERSION),--push)
.PHONY: docker-branch docker-master docker-release-candidate docker-release
# XXX We put the output for the build in 'output' so we don't mess with how we
# do rule overriding from the base Makefile (if you name it 'build' it messes up
# the wildcarding).
DOCKER_OUTPUT=$(OUTPUT_ROOT)docker/
DOCKER_MAKE=V=$V GOOS_OVERRIDE='GOOS=linux GOARCH=amd64' PREFIX=$(1) make $(1)bin/$(BINNAME)
DOCKER_BUILD=$Q docker build -t $(DOCKER_IMAGE_NAME):latest -f docker/Dockerfile.step-ca --build-arg BINPATH=$(DOCKER_OUTPUT)bin/$(BINNAME) .
docker-dev: docker/Dockerfile.step-ca
mkdir -p $(DOCKER_OUTPUT)
$(call DOCKER_MAKE,$(DOCKER_OUTPUT),step-ca)
$(call DOCKER_BUILD)
.PHONY: docker-dev

@ -17,6 +17,7 @@ type helmVariables struct {
Defaults *linkedca.Defaults
Password string
EnableSSH bool
EnableAdmin bool
TLS authconfig.TLSOptions
Provisioners []provisioner.Interface
}
@ -34,14 +35,39 @@ func (p *PKI) WriteHelmTemplate(w io.Writer) error {
p.Ssh = nil
}
// Convert provisioner to ca.json
provisioners := make([]provisioner.Interface, len(p.Authority.Provisioners))
for i, p := range p.Authority.Provisioners {
// Convert provisioners to ca.json representation
provisioners := []provisioner.Interface{}
for _, p := range p.Authority.Provisioners {
pp, err := authority.ProvisionerToCertificates(p)
if err != nil {
return err
}
provisioners[i] = pp
provisioners = append(provisioners, pp)
}
// Add default ACME provisioner if enabled. Note that this logic is similar
// to what's in p.GenerateConfig(), but that codepath isn't taken when
// writing the Helm template. The default JWK provisioner is added earlier in
// the process and that's part of the provisioners above.
// TODO(hs): consider refactoring the initialization, so that this becomes
// easier to reason about and maintain.
if p.options.enableACME {
provisioners = append(provisioners, &provisioner.ACME{
Type: "ACME",
Name: "acme",
})
}
// Add default SSHPOP provisioner if enabled. Similar to the above, this is
// the same as what happens in p.GenerateConfig().
if p.options.enableSSH {
provisioners = append(provisioners, &provisioner.SSHPOP{
Type: "SSHPOP",
Name: "sshpop",
Claims: &provisioner.Claims{
EnableSSHCA: &p.options.enableSSH,
},
})
}
if err := tmpl.Execute(w, helmVariables{
@ -49,6 +75,7 @@ func (p *PKI) WriteHelmTemplate(w io.Writer) error {
Defaults: &p.Defaults,
Password: "",
EnableSSH: p.options.enableSSH,
EnableAdmin: p.options.enableAdmin,
TLS: authconfig.DefaultTLSOptions,
Provisioners: provisioners,
}); err != nil {
@ -88,6 +115,7 @@ inject:
type: badgerv2
dataSource: /home/step/db
authority:
enableAdmin: {{ .EnableAdmin }}
provisioners:
{{- range .Provisioners }}
- {{ . | toJson }}

@ -0,0 +1,232 @@
package pki
import (
"bytes"
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"encoding/json"
"encoding/pem"
"os"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"go.step.sm/crypto/jose"
"go.step.sm/linkedca"
"github.com/smallstep/certificates/cas/apiv1"
)
func TestPKI_WriteHelmTemplate(t *testing.T) {
var preparePKI = func(t *testing.T, opts ...Option) *PKI {
o := apiv1.Options{
Type: "softcas",
IsCreator: true,
}
// Add default WithHelm option
opts = append(opts, WithHelm())
// TODO(hs): invoking `New` doesn't perform all operations that are executed
// when `ca init --helm` is executed. Ideally this logic should be handled
// in one place and probably inside of the PKI initialization. For testing
// purposes the missing operations to fill a Helm template fully are faked
// by `setKeyPair`, `setCertificates` and `setSSHSigningKeys`
p, err := New(o, opts...)
assert.NoError(t, err)
// setKeyPair sets a predefined JWK and a default JWK provisioner. This is one
// of the things performed in the `ca init` code that's not part of `New`, but
// performed after that in p.GenerateKeyPairs`. We're currently using the same
// JWK for every test to keep test variance small: we're not testing JWK generation
// here after all. It's a bit dangerous to redefine the function here, but it's
// the simplest way to make this fully testable without refactoring the init now.
// The password for the predefined encrypted key is \x01\x03\x03\x07.
setKeyPair(t, p)
// setCertificates sets some static intermediate and root CA certificate bytes. It
// replaces the logic executed in `p.GenerateRootCertificate`, `p.WriteRootCertificate`,
// and `p.GenerateIntermediateCertificate`.
setCertificates(t, p)
// setSSHSigningKeys sets predefined SSH user and host certificate and key bytes.
// This replaces the logic in `p.GenerateSSHSigningKeys`
setSSHSigningKeys(t, p)
return p
}
type test struct {
pki *PKI
testFile string
wantErr bool
}
var tests = map[string]func(t *testing.T) test{
"ok/simple": func(t *testing.T) test {
return test{
pki: preparePKI(t),
testFile: "testdata/helm/simple.yml",
wantErr: false,
}
},
"ok/with-provisioner": func(t *testing.T) test {
return test{
pki: preparePKI(t, WithProvisioner("a-provisioner")),
testFile: "testdata/helm/with-provisioner.yml",
wantErr: false,
}
},
"ok/with-acme": func(t *testing.T) test {
return test{
pki: preparePKI(t, WithACME()),
testFile: "testdata/helm/with-acme.yml",
wantErr: false,
}
},
"ok/with-admin": func(t *testing.T) test {
return test{
pki: preparePKI(t, WithAdmin()),
testFile: "testdata/helm/with-admin.yml",
wantErr: false,
}
},
"ok/with-ssh": func(t *testing.T) test {
return test{
pki: preparePKI(t, WithSSH()),
testFile: "testdata/helm/with-ssh.yml",
wantErr: false,
}
},
"ok/with-ssh-and-acme": func(t *testing.T) test {
return test{
pki: preparePKI(t, WithSSH(), WithACME()),
testFile: "testdata/helm/with-ssh-and-acme.yml",
wantErr: false,
}
},
"fail/authority.ProvisionerToCertificates": func(t *testing.T) test {
pki := preparePKI(t)
pki.Authority.Provisioners = append(pki.Authority.Provisioners,
&linkedca.Provisioner{
Type: linkedca.Provisioner_JWK,
Name: "Broken JWK",
Details: nil,
},
)
return test{
pki: pki,
wantErr: true,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
w := &bytes.Buffer{}
if err := tc.pki.WriteHelmTemplate(w); (err != nil) != tc.wantErr {
t.Errorf("PKI.WriteHelmTemplate() error = %v, wantErr %v", err, tc.wantErr)
return
}
if tc.wantErr {
// don't compare output if an error was expected on output
return
}
wantBytes, err := os.ReadFile(tc.testFile)
assert.NoError(t, err)
if diff := cmp.Diff(wantBytes, w.Bytes()); diff != "" {
t.Logf("Generated Helm template did not match reference %q\n", tc.testFile)
t.Errorf("Diff follows:\n%s\n", diff)
t.Errorf("Full output:\n%s\n", w.Bytes())
}
})
}
}
// setKeyPair sets a predefined JWK and a default JWK provisioner.
func setKeyPair(t *testing.T, p *PKI) {
t.Helper()
var err error
p.ottPublicKey, err = jose.ParseKey([]byte(`{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"}`))
if err != nil {
t.Fatal(err)
}
p.ottPrivateKey, err = jose.ParseEncrypted("eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA")
if err != nil {
t.Fatal(err)
}
var claims *linkedca.Claims
if p.options.enableSSH {
claims = &linkedca.Claims{
Ssh: &linkedca.SSHClaims{
Enabled: true,
},
}
}
publicKey, err := json.Marshal(p.ottPublicKey)
if err != nil {
t.Fatal(err)
}
encryptedKey, err := p.ottPrivateKey.CompactSerialize()
if err != nil {
t.Fatal(err)
}
p.Authority.Provisioners = append(p.Authority.Provisioners, &linkedca.Provisioner{
Type: linkedca.Provisioner_JWK,
Name: p.options.provisioner,
Claims: claims,
Details: &linkedca.ProvisionerDetails{
Data: &linkedca.ProvisionerDetails_JWK{
JWK: &linkedca.JWKProvisioner{
PublicKey: publicKey,
EncryptedPrivateKey: []byte(encryptedKey),
},
},
},
})
}
// setCertificates sets some static, gibberish intermediate and root CA certificate and key bytes.
func setCertificates(t *testing.T, p *PKI) {
raw := []byte("these are just some fake root CA cert bytes")
p.Files[p.Root[0]] = encodeCertificate(&x509.Certificate{Raw: raw})
p.Files[p.RootKey[0]] = pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: []byte("these are just some fake root CA key bytes"),
})
p.Files[p.Intermediate] = encodeCertificate(&x509.Certificate{Raw: []byte("these are just some fake intermediate CA cert bytes")})
p.Files[p.IntermediateKey] = pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: []byte("these are just some fake intermediate CA key bytes"),
})
sum := sha256.Sum256(raw)
p.Defaults.Fingerprint = strings.ToLower(hex.EncodeToString(sum[:]))
}
// setSSHSigningKeys sets some static, gibberish ssh user and host CA certificate and key bytes.
func setSSHSigningKeys(t *testing.T, p *PKI) {
if !p.options.enableSSH {
return
}
p.Files[p.Ssh.HostKey] = pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: []byte("fake ssh host key bytes"),
})
p.Files[p.Ssh.HostPublicKey] = []byte("ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ0IdS5sZm6KITBMZLEJD6b5ROVraYHcAOr3feFel8r1Wp4DRPR1oU0W00J/zjNBRBbANlJoYN4x/8WNNVZ49Ms=")
p.Files[p.Ssh.UserKey] = pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: []byte("fake ssh user key bytes"),
})
p.Files[p.Ssh.UserPublicKey] = []byte("ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEWA1qUxaGwVNErsvEOGe2d6TvLMF+aiVpuOiIEvpMJ3JeJmecLQctjWqeIbpSvy6/gRa7c82Ge5rLlapYmOChs=")
}

@ -176,6 +176,7 @@ func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
type options struct {
provisioner string
superAdminSubject string
pkiOnly bool
enableACME bool
enableSSH bool
@ -220,6 +221,15 @@ func WithProvisioner(s string) Option {
}
}
// WithSuperAdminSubject defines the subject of the first
// super admin for use with the Admin API. The admin will belong
// to the first JWK provisioner.
func WithSuperAdminSubject(s string) Option {
return func(p *PKI) {
p.options.superAdminSubject = s
}
}
// WithPKIOnly will only generate the PKI without the step-ca config files.
func WithPKIOnly() Option {
return func(p *PKI) {
@ -307,6 +317,9 @@ type PKI struct {
// New creates a new PKI configuration.
func New(o apiv1.Options, opts ...Option) (*PKI, error) {
// TODO(hs): invoking `New` with a context active will use values from
// that CA context while generating the context. Thay may or may not
// be fully expected and/or what we want. Check that.
currentCtx := step.Contexts().GetCurrent()
caService, err := cas.New(context.Background(), o)
if err != nil {
@ -645,7 +658,7 @@ func (p *PKI) GetCertificateAuthority() error {
// SSH user certificates and a private key used for signing host certificates.
func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
// Enable SSH
p.options.enableSSH = true
p.options.enableSSH = true // TODO(hs): change this function to not mutate configuration state
// Create SSH key used to sign host certificates. Using
// kmsapi.UnspecifiedSignAlgorithm will default to the default algorithm.
@ -883,6 +896,11 @@ func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) {
//
// Note that we might want to be able to define the database as a
// flag in `step ca init` so we can write to the proper place.
//
// TODO(hs): the logic for creating the provisioners and the super admin
// is similar to what's done when automatically migrating the provisioners.
// This is related to the existing comment above. Refactor this to exist in
// a single place and ensure it happens only once.
_db, err := db.New(cfg.DB)
if err != nil {
return nil, err
@ -906,9 +924,13 @@ func (p *PKI) GenerateConfig(opt ...ConfigOption) (*authconfig.Config, error) {
}
}
// Add the first provisioner as an admin.
superAdminSubject := "step"
if p.options.superAdminSubject != "" {
superAdminSubject = p.options.superAdminSubject
}
if err := adminDB.CreateAdmin(context.Background(), &linkedca.Admin{
AuthorityId: admin.DefaultAuthorityID,
Subject: "step",
Subject: superAdminSubject,
Type: linkedca.Admin_SUPER_ADMIN,
ProvisionerId: adminID,
}); err != nil {
@ -991,6 +1013,18 @@ func (p *PKI) Save(opt ...ConfigOption) error {
ui.PrintSelected("Default profile configuration", p.profileDefaults)
}
ui.PrintSelected("Certificate Authority configuration", p.config)
if cfg.AuthorityConfig.EnableAdmin && p.options.deploymentType != LinkedDeployment {
// TODO(hs): we may want to get this information from the DB, because that's
// where the admin and provisioner are stored in this case. Requires some
// refactoring.
superAdminSubject := "step"
if p.options.superAdminSubject != "" {
superAdminSubject = p.options.superAdminSubject
}
ui.PrintSelected("Admin provisioner", fmt.Sprintf("%s (JWK)", p.options.provisioner))
ui.PrintSelected("Super admin subject", superAdminSubject)
}
if p.options.deploymentType != LinkedDeployment {
ui.Println()
if p.casOptions.Is(apiv1.SoftCAS) {

@ -0,0 +1,81 @@
# Helm template
inject:
enabled: true
# Config contains the configuration files ca.json and defaults.json
config:
files:
ca.json:
root: /home/step/certs/root_ca.crt
federateRoots: []
crt: /home/step/certs/intermediate_ca.crt
key: /home/step/secrets/intermediate_ca_key
address: 127.0.0.1:9000
dnsNames:
- 127.0.0.1
logger:
format: json
db:
type: badgerv2
dataSource: /home/step/db
authority:
enableAdmin: false
provisioners:
- {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","options":{"x509":{},"ssh":{}}}
tls:
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
minVersion: 1.2
maxVersion: 1.3
renegotiation: false
defaults.json:
ca-url: https://127.0.0.1
ca-config: /home/step/config/ca.json
fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3
root: /home/step/certs/root_ca.crt
# Certificates contains the root and intermediate certificate and
# optionally the SSH host and user public keys
certificates:
# intermediate_ca contains the text of the intermediate CA Certificate
intermediate_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5
dGVz
-----END CERTIFICATE-----
# root_ca contains the text of the root CA Certificate
root_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw==
-----END CERTIFICATE-----
# Secrets contains the root and intermediate keys and optionally the SSH
# private keys
secrets:
# ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key
# This value must be base64 encoded.
ca_password:
provisioner_password:
x509:
# intermediate_ca_key contains the contents of your encrypted intermediate CA key
intermediate_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0
ZXM=
-----END EC PRIVATE KEY-----
# root_ca_key contains the contents of your encrypted root CA key
# Note that this value can be omitted without impacting the functionality of step-certificates
# If supplied, this should be encrypted using a unique password that is not used for encrypting
# the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key.
root_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz
-----END EC PRIVATE KEY-----

@ -0,0 +1,82 @@
# Helm template
inject:
enabled: true
# Config contains the configuration files ca.json and defaults.json
config:
files:
ca.json:
root: /home/step/certs/root_ca.crt
federateRoots: []
crt: /home/step/certs/intermediate_ca.crt
key: /home/step/secrets/intermediate_ca_key
address: 127.0.0.1:9000
dnsNames:
- 127.0.0.1
logger:
format: json
db:
type: badgerv2
dataSource: /home/step/db
authority:
enableAdmin: false
provisioners:
- {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","options":{"x509":{},"ssh":{}}}
- {"type":"ACME","name":"acme"}
tls:
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
minVersion: 1.2
maxVersion: 1.3
renegotiation: false
defaults.json:
ca-url: https://127.0.0.1
ca-config: /home/step/config/ca.json
fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3
root: /home/step/certs/root_ca.crt
# Certificates contains the root and intermediate certificate and
# optionally the SSH host and user public keys
certificates:
# intermediate_ca contains the text of the intermediate CA Certificate
intermediate_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5
dGVz
-----END CERTIFICATE-----
# root_ca contains the text of the root CA Certificate
root_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw==
-----END CERTIFICATE-----
# Secrets contains the root and intermediate keys and optionally the SSH
# private keys
secrets:
# ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key
# This value must be base64 encoded.
ca_password:
provisioner_password:
x509:
# intermediate_ca_key contains the contents of your encrypted intermediate CA key
intermediate_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0
ZXM=
-----END EC PRIVATE KEY-----
# root_ca_key contains the contents of your encrypted root CA key
# Note that this value can be omitted without impacting the functionality of step-certificates
# If supplied, this should be encrypted using a unique password that is not used for encrypting
# the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key.
root_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz
-----END EC PRIVATE KEY-----

@ -0,0 +1,81 @@
# Helm template
inject:
enabled: true
# Config contains the configuration files ca.json and defaults.json
config:
files:
ca.json:
root: /home/step/certs/root_ca.crt
federateRoots: []
crt: /home/step/certs/intermediate_ca.crt
key: /home/step/secrets/intermediate_ca_key
address: 127.0.0.1:9000
dnsNames:
- 127.0.0.1
logger:
format: json
db:
type: badgerv2
dataSource: /home/step/db
authority:
enableAdmin: true
provisioners:
- {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","options":{"x509":{},"ssh":{}}}
tls:
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
minVersion: 1.2
maxVersion: 1.3
renegotiation: false
defaults.json:
ca-url: https://127.0.0.1
ca-config: /home/step/config/ca.json
fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3
root: /home/step/certs/root_ca.crt
# Certificates contains the root and intermediate certificate and
# optionally the SSH host and user public keys
certificates:
# intermediate_ca contains the text of the intermediate CA Certificate
intermediate_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5
dGVz
-----END CERTIFICATE-----
# root_ca contains the text of the root CA Certificate
root_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw==
-----END CERTIFICATE-----
# Secrets contains the root and intermediate keys and optionally the SSH
# private keys
secrets:
# ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key
# This value must be base64 encoded.
ca_password:
provisioner_password:
x509:
# intermediate_ca_key contains the contents of your encrypted intermediate CA key
intermediate_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0
ZXM=
-----END EC PRIVATE KEY-----
# root_ca_key contains the contents of your encrypted root CA key
# Note that this value can be omitted without impacting the functionality of step-certificates
# If supplied, this should be encrypted using a unique password that is not used for encrypting
# the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key.
root_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz
-----END EC PRIVATE KEY-----

@ -0,0 +1,81 @@
# Helm template
inject:
enabled: true
# Config contains the configuration files ca.json and defaults.json
config:
files:
ca.json:
root: /home/step/certs/root_ca.crt
federateRoots: []
crt: /home/step/certs/intermediate_ca.crt
key: /home/step/secrets/intermediate_ca_key
address: 127.0.0.1:9000
dnsNames:
- 127.0.0.1
logger:
format: json
db:
type: badgerv2
dataSource: /home/step/db
authority:
enableAdmin: false
provisioners:
- {"type":"JWK","name":"a-provisioner","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","options":{"x509":{},"ssh":{}}}
tls:
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
minVersion: 1.2
maxVersion: 1.3
renegotiation: false
defaults.json:
ca-url: https://127.0.0.1
ca-config: /home/step/config/ca.json
fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3
root: /home/step/certs/root_ca.crt
# Certificates contains the root and intermediate certificate and
# optionally the SSH host and user public keys
certificates:
# intermediate_ca contains the text of the intermediate CA Certificate
intermediate_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5
dGVz
-----END CERTIFICATE-----
# root_ca contains the text of the root CA Certificate
root_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw==
-----END CERTIFICATE-----
# Secrets contains the root and intermediate keys and optionally the SSH
# private keys
secrets:
# ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key
# This value must be base64 encoded.
ca_password:
provisioner_password:
x509:
# intermediate_ca_key contains the contents of your encrypted intermediate CA key
intermediate_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0
ZXM=
-----END EC PRIVATE KEY-----
# root_ca_key contains the contents of your encrypted root CA key
# Note that this value can be omitted without impacting the functionality of step-certificates
# If supplied, this should be encrypted using a unique password that is not used for encrypting
# the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key.
root_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz
-----END EC PRIVATE KEY-----

@ -0,0 +1,105 @@
# Helm template
inject:
enabled: true
# Config contains the configuration files ca.json and defaults.json
config:
files:
ca.json:
root: /home/step/certs/root_ca.crt
federateRoots: []
crt: /home/step/certs/intermediate_ca.crt
key: /home/step/secrets/intermediate_ca_key
ssh:
hostKey: /home/step/secrets/ssh_host_ca_key
userKey: /home/step/secrets/ssh_user_ca_key
address: 127.0.0.1:9000
dnsNames:
- 127.0.0.1
logger:
format: json
db:
type: badgerv2
dataSource: /home/step/db
authority:
enableAdmin: false
provisioners:
- {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","claims":{"enableSSHCA":true,"disableRenewal":false,"allowRenewalAfterExpiry":false},"options":{"x509":{},"ssh":{}}}
- {"type":"ACME","name":"acme"}
- {"type":"SSHPOP","name":"sshpop","claims":{"enableSSHCA":true}}
tls:
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
minVersion: 1.2
maxVersion: 1.3
renegotiation: false
defaults.json:
ca-url: https://127.0.0.1
ca-config: /home/step/config/ca.json
fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3
root: /home/step/certs/root_ca.crt
# Certificates contains the root and intermediate certificate and
# optionally the SSH host and user public keys
certificates:
# intermediate_ca contains the text of the intermediate CA Certificate
intermediate_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5
dGVz
-----END CERTIFICATE-----
# root_ca contains the text of the root CA Certificate
root_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw==
-----END CERTIFICATE-----
# ssh_host_ca contains the text of the public ssh key for the SSH root CA
ssh_host_ca: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ0IdS5sZm6KITBMZLEJD6b5ROVraYHcAOr3feFel8r1Wp4DRPR1oU0W00J/zjNBRBbANlJoYN4x/8WNNVZ49Ms=
# ssh_user_ca contains the text of the public ssh key for the SSH root CA
ssh_user_ca: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEWA1qUxaGwVNErsvEOGe2d6TvLMF+aiVpuOiIEvpMJ3JeJmecLQctjWqeIbpSvy6/gRa7c82Ge5rLlapYmOChs=
# Secrets contains the root and intermediate keys and optionally the SSH
# private keys
secrets:
# ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key
# This value must be base64 encoded.
ca_password:
provisioner_password:
x509:
# intermediate_ca_key contains the contents of your encrypted intermediate CA key
intermediate_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0
ZXM=
-----END EC PRIVATE KEY-----
# root_ca_key contains the contents of your encrypted root CA key
# Note that this value can be omitted without impacting the functionality of step-certificates
# If supplied, this should be encrypted using a unique password that is not used for encrypting
# the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key.
root_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz
-----END EC PRIVATE KEY-----
ssh:
# ssh_host_ca_key contains the contents of your encrypted SSH Host CA key
host_ca_key: |
-----BEGIN EC PRIVATE KEY-----
ZmFrZSBzc2ggaG9zdCBrZXkgYnl0ZXM=
-----END EC PRIVATE KEY-----
# ssh_user_ca_key contains the contents of your encrypted SSH User CA key
user_ca_key: |
-----BEGIN EC PRIVATE KEY-----
ZmFrZSBzc2ggdXNlciBrZXkgYnl0ZXM=
-----END EC PRIVATE KEY-----

@ -0,0 +1,104 @@
# Helm template
inject:
enabled: true
# Config contains the configuration files ca.json and defaults.json
config:
files:
ca.json:
root: /home/step/certs/root_ca.crt
federateRoots: []
crt: /home/step/certs/intermediate_ca.crt
key: /home/step/secrets/intermediate_ca_key
ssh:
hostKey: /home/step/secrets/ssh_host_ca_key
userKey: /home/step/secrets/ssh_user_ca_key
address: 127.0.0.1:9000
dnsNames:
- 127.0.0.1
logger:
format: json
db:
type: badgerv2
dataSource: /home/step/db
authority:
enableAdmin: false
provisioners:
- {"type":"JWK","name":"step-cli","key":{"use":"sig","kty":"EC","kid":"zsUmysmDVoGJ71YoPHyZ-68tNihDaDaO5Mu7xX3M-_I","crv":"P-256","alg":"ES256","x":"Pqnua4CzqKz6ua41J3yeWZ1sRkGt0UlCkbHv8H2DGuY","y":"UhoZ_2ItDen9KQTcjay-ph-SBXH0mwqhHyvrrqIFDOI"},"encryptedKey":"eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiZjVvdGVRS2hvOXl4MmQtSGlMZi05QSJ9.eYA6tt3fNuUpoxKWDT7P0Lbn2juxhEbTxEnwEMbjlYLLQ3sxL-dYTA.ven-FhmdjlC9itH0.a2jRTarN9vPd6F_mWnNBlOn6KbfMjCApmci2t65XbAsLzYFzhI_79Ykm5ueMYTupWLTjBJctl-g51ZHmsSB55pStbpoyyLNAsUX2E1fTmHe-Ni8bRrspwLv15FoN1Xo1g0mpR-ufWIFxOsW-QIfnMmMIIkygVuHFXmg2tFpzTNNG5aS29K3dN2nyk0WJrdIq79hZSTqVkkBU25Yu3A46sgjcM86XcIJJ2XUEih_KWEa6T1YrkixGu96pebjVqbO0R6dbDckfPF7FqNnwPHVtb1ACFpEYoOJVIbUCMaARBpWsxYhjJZlEM__XA46l8snFQDkNY3CdN0p1_gF3ckA.JLmq9nmu1h9oUi1S8ZxYjA","claims":{"enableSSHCA":true,"disableRenewal":false,"allowRenewalAfterExpiry":false},"options":{"x509":{},"ssh":{}}}
- {"type":"SSHPOP","name":"sshpop","claims":{"enableSSHCA":true}}
tls:
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
minVersion: 1.2
maxVersion: 1.3
renegotiation: false
defaults.json:
ca-url: https://127.0.0.1
ca-config: /home/step/config/ca.json
fingerprint: e543cad8e9f6417076bb5aed3471c588152118aac1e0ca7984a43ee7f76da5e3
root: /home/step/certs/root_ca.crt
# Certificates contains the root and intermediate certificate and
# optionally the SSH host and user public keys
certificates:
# intermediate_ca contains the text of the intermediate CA Certificate
intermediate_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBjZXJ0IGJ5
dGVz
-----END CERTIFICATE-----
# root_ca contains the text of the root CA Certificate
root_ca: |
-----BEGIN CERTIFICATE-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0EgY2VydCBieXRlcw==
-----END CERTIFICATE-----
# ssh_host_ca contains the text of the public ssh key for the SSH root CA
ssh_host_ca: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ0IdS5sZm6KITBMZLEJD6b5ROVraYHcAOr3feFel8r1Wp4DRPR1oU0W00J/zjNBRBbANlJoYN4x/8WNNVZ49Ms=
# ssh_user_ca contains the text of the public ssh key for the SSH root CA
ssh_user_ca: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEWA1qUxaGwVNErsvEOGe2d6TvLMF+aiVpuOiIEvpMJ3JeJmecLQctjWqeIbpSvy6/gRa7c82Ge5rLlapYmOChs=
# Secrets contains the root and intermediate keys and optionally the SSH
# private keys
secrets:
# ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key
# This value must be base64 encoded.
ca_password:
provisioner_password:
x509:
# intermediate_ca_key contains the contents of your encrypted intermediate CA key
intermediate_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIGludGVybWVkaWF0ZSBDQSBrZXkgYnl0
ZXM=
-----END EC PRIVATE KEY-----
# root_ca_key contains the contents of your encrypted root CA key
# Note that this value can be omitted without impacting the functionality of step-certificates
# If supplied, this should be encrypted using a unique password that is not used for encrypting
# the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key.
root_ca_key: |
-----BEGIN EC PRIVATE KEY-----
dGhlc2UgYXJlIGp1c3Qgc29tZSBmYWtlIHJvb3QgQ0Ega2V5IGJ5dGVz
-----END EC PRIVATE KEY-----
ssh:
# ssh_host_ca_key contains the contents of your encrypted SSH Host CA key
host_ca_key: |
-----BEGIN EC PRIVATE KEY-----
ZmFrZSBzc2ggaG9zdCBrZXkgYnl0ZXM=
-----END EC PRIVATE KEY-----
# ssh_user_ca_key contains the contents of your encrypted SSH User CA key
user_ca_key: |
-----BEGIN EC PRIVATE KEY-----
ZmFrZSBzc2ggdXNlciBrZXkgYnl0ZXM=
-----END EC PRIVATE KEY-----
Loading…
Cancel
Save