Merge branch 'master' into herman/ignore-empty-acme-meta
This commit is contained in:
commit
02d679e160
37 changed files with 2004 additions and 277 deletions
75
.github/workflows/release.yml
vendored
75
.github/workflows/release.yml
vendored
|
@ -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).
|
||||
|
|
10
Makefile
10
Makefile
|
@ -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
|
||||
|
|
32
api/crl.go
Normal file
32
api/crl.go
Normal file
|
@ -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 admin
|
||||
// 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 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 {
|
||||
|
|
243
authority/tls.go
243
authority/tls.go
|
@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
13
ca/ca.go
13
ca/ca.go
|
@ -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
|
||||
|
|
120
db/db.go
120
db/db.go
|
@ -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.
|
||||
|
|
15
db/simple.go
15
db/simple.go
|
@ -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.
|
||||
|
|
29
go.mod
29
go.mod
|
@ -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
|
||||
|
|
59
go.sum
59
go.sum
|
@ -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
|
36
pki/helm.go
36
pki/helm.go
|
@ -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 }}
|
||||
|
|
232
pki/helm_test.go
Normal file
232
pki/helm_test.go
Normal file
|
@ -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=")
|
||||
}
|
38
pki/pki.go
38
pki/pki.go
|
@ -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) {
|
||||
|
|
81
pki/testdata/helm/simple.yml
vendored
Normal file
81
pki/testdata/helm/simple.yml
vendored
Normal file
|
@ -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-----
|
||||
|
82
pki/testdata/helm/with-acme.yml
vendored
Normal file
82
pki/testdata/helm/with-acme.yml
vendored
Normal file
|
@ -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-----
|
||||
|
81
pki/testdata/helm/with-admin.yml
vendored
Normal file
81
pki/testdata/helm/with-admin.yml
vendored
Normal file
|
@ -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-----
|
||||
|
81
pki/testdata/helm/with-provisioner.yml
vendored
Normal file
81
pki/testdata/helm/with-provisioner.yml
vendored
Normal file
|
@ -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-----
|
||||
|
105
pki/testdata/helm/with-ssh-and-acme.yml
vendored
Normal file
105
pki/testdata/helm/with-ssh-and-acme.yml
vendored
Normal file
|
@ -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-----
|
||||
|
104
pki/testdata/helm/with-ssh.yml
vendored
Normal file
104
pki/testdata/helm/with-ssh.yml
vendored
Normal file
|
@ -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…
Reference in a new issue