forked from TrueCloudLab/certificates
Merge branch 'master' into hs/acme-revocation
This commit is contained in:
commit
8f7e700f09
83 changed files with 8009 additions and 907 deletions
|
@ -8,6 +8,8 @@ linters-settings:
|
|||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
|
||||
revive:
|
||||
min-confidence: 0
|
||||
gocyclo:
|
||||
min-complexity: 10
|
||||
maligned:
|
||||
|
|
2
Makefile
2
Makefile
|
@ -29,7 +29,7 @@ ci: testcgo build
|
|||
|
||||
bootstra%:
|
||||
# Using a released version of golangci-lint to take into account custom replacements in their go.mod
|
||||
$Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.39.0
|
||||
$Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.39.0
|
||||
|
||||
.PHONY: bootstra%
|
||||
|
||||
|
|
|
@ -574,13 +574,13 @@ func TestHandler_GetChallenge(t *testing.T) {
|
|||
assert.Equals(t, azID, "authzID")
|
||||
return &acme.Challenge{
|
||||
Status: acme.StatusPending,
|
||||
Type: "http-01",
|
||||
Type: acme.HTTP01,
|
||||
AccountID: "accID",
|
||||
}, nil
|
||||
},
|
||||
MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
|
||||
assert.Equals(t, ch.Status, acme.StatusPending)
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
assert.Equals(t, ch.AccountID, "accID")
|
||||
assert.Equals(t, ch.AuthorizationID, "authzID")
|
||||
assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String())
|
||||
|
@ -616,13 +616,13 @@ func TestHandler_GetChallenge(t *testing.T) {
|
|||
return &acme.Challenge{
|
||||
ID: "chID",
|
||||
Status: acme.StatusPending,
|
||||
Type: "http-01",
|
||||
Type: acme.HTTP01,
|
||||
AccountID: "accID",
|
||||
}, nil
|
||||
},
|
||||
MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
|
||||
assert.Equals(t, ch.Status, acme.StatusPending)
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
assert.Equals(t, ch.AccountID, "accID")
|
||||
assert.Equals(t, ch.AuthorizationID, "authzID")
|
||||
assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String())
|
||||
|
@ -633,7 +633,7 @@ func TestHandler_GetChallenge(t *testing.T) {
|
|||
ID: "chID",
|
||||
Status: acme.StatusPending,
|
||||
AuthorizationID: "authzID",
|
||||
Type: "http-01",
|
||||
Type: acme.HTTP01,
|
||||
AccountID: "accID",
|
||||
URL: url,
|
||||
Error: acme.NewError(acme.ErrorConnectionType, "force"),
|
||||
|
|
|
@ -288,13 +288,13 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP {
|
|||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
name := chi.URLParam(r, "provisionerID")
|
||||
provID, err := url.PathUnescape(name)
|
||||
nameEscaped := chi.URLParam(r, "provisionerID")
|
||||
name, err := url.PathUnescape(nameEscaped)
|
||||
if err != nil {
|
||||
api.WriteError(w, acme.WrapErrorISE(err, "error url unescaping provisioner id '%s'", name))
|
||||
api.WriteError(w, acme.WrapErrorISE(err, "error url unescaping provisioner name '%s'", nameEscaped))
|
||||
return
|
||||
}
|
||||
p, err := h.ca.LoadProvisionerByID("acme/" + provID)
|
||||
p, err := h.ca.LoadProvisionerByName(name)
|
||||
if err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -28,9 +29,12 @@ func (n *NewOrderRequest) Validate() error {
|
|||
return acme.NewError(acme.ErrorMalformedType, "identifiers list cannot be empty")
|
||||
}
|
||||
for _, id := range n.Identifiers {
|
||||
if id.Type != "dns" {
|
||||
if !(id.Type == acme.DNS || id.Type == acme.IP) {
|
||||
return acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: %s", id.Type)
|
||||
}
|
||||
if id.Type == acme.IP && net.ParseIP(id.Value) == nil {
|
||||
return acme.NewError(acme.ErrorMalformedType, "invalid IP address: %s", id.Value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -85,6 +89,7 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) {
|
|||
"failed to unmarshal new-order request payload"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := nor.Validate(); err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
|
@ -149,15 +154,9 @@ func (h *Handler) newAuthorization(ctx context.Context, az *acme.Authorization)
|
|||
}
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
chTypes = []string{"dns-01"}
|
||||
)
|
||||
// HTTP and TLS challenges can only be used for identifiers without wildcards.
|
||||
if !az.Wildcard {
|
||||
chTypes = append(chTypes, []string{"http-01", "tls-alpn-01"}...)
|
||||
}
|
||||
chTypes := challengeTypes(az)
|
||||
|
||||
var err error
|
||||
az.Token, err = randutil.Alphanumeric(32)
|
||||
if err != nil {
|
||||
return acme.WrapErrorISE(err, "error generating random alphanumeric ID")
|
||||
|
@ -275,3 +274,24 @@ func (h *Handler) FinalizeOrder(w http.ResponseWriter, r *http.Request) {
|
|||
w.Header().Set("Location", h.linker.GetLink(ctx, OrderLinkType, o.ID))
|
||||
api.JSON(w, o)
|
||||
}
|
||||
|
||||
// challengeTypes determines the types of challenges that should be used
|
||||
// for the ACME authorization request.
|
||||
func challengeTypes(az *acme.Authorization) []acme.ChallengeType {
|
||||
var chTypes []acme.ChallengeType
|
||||
|
||||
switch az.Identifier.Type {
|
||||
case acme.IP:
|
||||
chTypes = []acme.ChallengeType{acme.HTTP01, acme.TLSALPN01}
|
||||
case acme.DNS:
|
||||
chTypes = []acme.ChallengeType{acme.DNS01}
|
||||
// HTTP and TLS challenges can only be used for identifiers without wildcards.
|
||||
if !az.Wildcard {
|
||||
chTypes = append(chTypes, []acme.ChallengeType{acme.HTTP01, acme.TLSALPN01}...)
|
||||
}
|
||||
default:
|
||||
chTypes = []acme.ChallengeType{}
|
||||
}
|
||||
|
||||
return chTypes
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -44,6 +45,22 @@ func TestNewOrderRequest_Validate(t *testing.T) {
|
|||
err: acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: foo"),
|
||||
}
|
||||
},
|
||||
"fail/bad-ip": func(t *testing.T) test {
|
||||
nbf := time.Now().UTC().Add(time.Minute)
|
||||
naf := time.Now().UTC().Add(5 * time.Minute)
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "ip", Value: "192.168.42.1000"},
|
||||
},
|
||||
NotAfter: naf,
|
||||
NotBefore: nbf,
|
||||
},
|
||||
nbf: nbf,
|
||||
naf: naf,
|
||||
err: acme.NewError(acme.ErrorMalformedType, "invalid IP address: %s", "192.168.42.1000"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
nbf := time.Now().UTC().Add(time.Minute)
|
||||
naf := time.Now().UTC().Add(5 * time.Minute)
|
||||
|
@ -60,6 +77,68 @@ func TestNewOrderRequest_Validate(t *testing.T) {
|
|||
naf: naf,
|
||||
}
|
||||
},
|
||||
"ok/ipv4": func(t *testing.T) test {
|
||||
nbf := time.Now().UTC().Add(time.Minute)
|
||||
naf := time.Now().UTC().Add(5 * time.Minute)
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
},
|
||||
NotAfter: naf,
|
||||
NotBefore: nbf,
|
||||
},
|
||||
nbf: nbf,
|
||||
naf: naf,
|
||||
}
|
||||
},
|
||||
"ok/ipv6": func(t *testing.T) test {
|
||||
nbf := time.Now().UTC().Add(time.Minute)
|
||||
naf := time.Now().UTC().Add(5 * time.Minute)
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "ip", Value: "2001:db8::1"},
|
||||
},
|
||||
NotAfter: naf,
|
||||
NotBefore: nbf,
|
||||
},
|
||||
nbf: nbf,
|
||||
naf: naf,
|
||||
}
|
||||
},
|
||||
"ok/mixed-dns-and-ipv4": func(t *testing.T) test {
|
||||
nbf := time.Now().UTC().Add(time.Minute)
|
||||
naf := time.Now().UTC().Add(5 * time.Minute)
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "dns", Value: "example.com"},
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
},
|
||||
NotAfter: naf,
|
||||
NotBefore: nbf,
|
||||
},
|
||||
nbf: nbf,
|
||||
naf: naf,
|
||||
}
|
||||
},
|
||||
"ok/mixed-ipv4-and-ipv6": func(t *testing.T) test {
|
||||
nbf := time.Now().UTC().Add(time.Minute)
|
||||
naf := time.Now().UTC().Add(5 * time.Minute)
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
{Type: "ip", Value: "2001:db8::1"},
|
||||
},
|
||||
NotAfter: naf,
|
||||
NotBefore: nbf,
|
||||
},
|
||||
nbf: nbf,
|
||||
naf: naf,
|
||||
}
|
||||
},
|
||||
}
|
||||
for name, run := range tests {
|
||||
tc := run(t)
|
||||
|
@ -395,7 +474,7 @@ func TestHandler_newAuthorization(t *testing.T) {
|
|||
db: &acme.MockDB{
|
||||
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
|
||||
assert.Equals(t, ch.AccountID, az.AccountID)
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
assert.Equals(t, ch.Token, az.Token)
|
||||
assert.Equals(t, ch.Status, acme.StatusPending)
|
||||
assert.Equals(t, ch.Value, az.Identifier.Value)
|
||||
|
@ -424,15 +503,15 @@ func TestHandler_newAuthorization(t *testing.T) {
|
|||
switch count {
|
||||
case 0:
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
ch1 = &ch
|
||||
case 1:
|
||||
ch.ID = "http"
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
ch2 = &ch
|
||||
case 2:
|
||||
ch.ID = "tls"
|
||||
assert.Equals(t, ch.Type, "tls-alpn-01")
|
||||
assert.Equals(t, ch.Type, acme.TLSALPN01)
|
||||
ch3 = &ch
|
||||
default:
|
||||
assert.FatalError(t, errors.New("test logic error"))
|
||||
|
@ -478,15 +557,15 @@ func TestHandler_newAuthorization(t *testing.T) {
|
|||
switch count {
|
||||
case 0:
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
ch1 = &ch
|
||||
case 1:
|
||||
ch.ID = "http"
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
ch2 = &ch
|
||||
case 2:
|
||||
ch.ID = "tls"
|
||||
assert.Equals(t, ch.Type, "tls-alpn-01")
|
||||
assert.Equals(t, ch.Type, acme.TLSALPN01)
|
||||
ch3 = &ch
|
||||
default:
|
||||
assert.FatalError(t, errors.New("test logic error"))
|
||||
|
@ -528,7 +607,7 @@ func TestHandler_newAuthorization(t *testing.T) {
|
|||
db: &acme.MockDB{
|
||||
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
assert.Equals(t, ch.AccountID, az.AccountID)
|
||||
assert.Equals(t, ch.Token, az.Token)
|
||||
assert.Equals(t, ch.Status, acme.StatusPending)
|
||||
|
@ -695,7 +774,7 @@ func TestHandler_NewOrder(t *testing.T) {
|
|||
db: &acme.MockDB{
|
||||
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
|
||||
assert.Equals(t, ch.AccountID, "accID")
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
assert.NotEquals(t, ch.Token, "")
|
||||
assert.Equals(t, ch.Status, acme.StatusPending)
|
||||
assert.Equals(t, ch.Value, "zap.internal")
|
||||
|
@ -730,15 +809,15 @@ func TestHandler_NewOrder(t *testing.T) {
|
|||
switch count {
|
||||
case 0:
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
ch1 = &ch
|
||||
case 1:
|
||||
ch.ID = "http"
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
ch2 = &ch
|
||||
case 2:
|
||||
ch.ID = "tls"
|
||||
assert.Equals(t, ch.Type, "tls-alpn-01")
|
||||
assert.Equals(t, ch.Type, acme.TLSALPN01)
|
||||
ch3 = &ch
|
||||
default:
|
||||
assert.FatalError(t, errors.New("test logic error"))
|
||||
|
@ -802,22 +881,22 @@ func TestHandler_NewOrder(t *testing.T) {
|
|||
switch chCount {
|
||||
case 0:
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
assert.Equals(t, ch.Value, "zap.internal")
|
||||
ch1 = &ch
|
||||
case 1:
|
||||
ch.ID = "http"
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
assert.Equals(t, ch.Value, "zap.internal")
|
||||
ch2 = &ch
|
||||
case 2:
|
||||
ch.ID = "tls"
|
||||
assert.Equals(t, ch.Type, "tls-alpn-01")
|
||||
assert.Equals(t, ch.Type, acme.TLSALPN01)
|
||||
assert.Equals(t, ch.Value, "zap.internal")
|
||||
ch3 = &ch
|
||||
case 3:
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
assert.Equals(t, ch.Value, "zar.internal")
|
||||
ch4 = &ch
|
||||
default:
|
||||
|
@ -842,7 +921,7 @@ func TestHandler_NewOrder(t *testing.T) {
|
|||
az.ID = "az2ID"
|
||||
az2ID = &az.ID
|
||||
assert.Equals(t, az.Identifier, acme.Identifier{
|
||||
Type: "dns",
|
||||
Type: acme.DNS,
|
||||
Value: "zar.internal",
|
||||
})
|
||||
assert.Equals(t, az.Wildcard, true)
|
||||
|
@ -917,15 +996,15 @@ func TestHandler_NewOrder(t *testing.T) {
|
|||
switch count {
|
||||
case 0:
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
ch1 = &ch
|
||||
case 1:
|
||||
ch.ID = "http"
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
ch2 = &ch
|
||||
case 2:
|
||||
ch.ID = "tls"
|
||||
assert.Equals(t, ch.Type, "tls-alpn-01")
|
||||
assert.Equals(t, ch.Type, acme.TLSALPN01)
|
||||
ch3 = &ch
|
||||
default:
|
||||
assert.FatalError(t, errors.New("test logic error"))
|
||||
|
@ -1009,15 +1088,15 @@ func TestHandler_NewOrder(t *testing.T) {
|
|||
switch count {
|
||||
case 0:
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
ch1 = &ch
|
||||
case 1:
|
||||
ch.ID = "http"
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
ch2 = &ch
|
||||
case 2:
|
||||
ch.ID = "tls"
|
||||
assert.Equals(t, ch.Type, "tls-alpn-01")
|
||||
assert.Equals(t, ch.Type, acme.TLSALPN01)
|
||||
ch3 = &ch
|
||||
default:
|
||||
assert.FatalError(t, errors.New("test logic error"))
|
||||
|
@ -1100,15 +1179,15 @@ func TestHandler_NewOrder(t *testing.T) {
|
|||
switch count {
|
||||
case 0:
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
ch1 = &ch
|
||||
case 1:
|
||||
ch.ID = "http"
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
ch2 = &ch
|
||||
case 2:
|
||||
ch.ID = "tls"
|
||||
assert.Equals(t, ch.Type, "tls-alpn-01")
|
||||
assert.Equals(t, ch.Type, acme.TLSALPN01)
|
||||
ch3 = &ch
|
||||
default:
|
||||
assert.FatalError(t, errors.New("test logic error"))
|
||||
|
@ -1192,15 +1271,15 @@ func TestHandler_NewOrder(t *testing.T) {
|
|||
switch count {
|
||||
case 0:
|
||||
ch.ID = "dns"
|
||||
assert.Equals(t, ch.Type, "dns-01")
|
||||
assert.Equals(t, ch.Type, acme.DNS01)
|
||||
ch1 = &ch
|
||||
case 1:
|
||||
ch.ID = "http"
|
||||
assert.Equals(t, ch.Type, "http-01")
|
||||
assert.Equals(t, ch.Type, acme.HTTP01)
|
||||
ch2 = &ch
|
||||
case 2:
|
||||
ch.ID = "tls"
|
||||
assert.Equals(t, ch.Type, "tls-alpn-01")
|
||||
assert.Equals(t, ch.Type, acme.TLSALPN01)
|
||||
ch3 = &ch
|
||||
default:
|
||||
assert.FatalError(t, errors.New("test logic error"))
|
||||
|
@ -1581,3 +1660,52 @@ func TestHandler_FinalizeOrder(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandler_challengeTypes(t *testing.T) {
|
||||
type args struct {
|
||||
az *acme.Authorization
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []acme.ChallengeType
|
||||
}{
|
||||
{
|
||||
name: "ok/dns",
|
||||
args: args{
|
||||
az: &acme.Authorization{
|
||||
Identifier: acme.Identifier{Type: "dns", Value: "example.com"},
|
||||
Wildcard: false,
|
||||
},
|
||||
},
|
||||
want: []acme.ChallengeType{acme.DNS01, acme.HTTP01, acme.TLSALPN01},
|
||||
},
|
||||
{
|
||||
name: "ok/wildcard",
|
||||
args: args{
|
||||
az: &acme.Authorization{
|
||||
Identifier: acme.Identifier{Type: "dns", Value: "*.example.com"},
|
||||
Wildcard: true,
|
||||
},
|
||||
},
|
||||
want: []acme.ChallengeType{acme.DNS01},
|
||||
},
|
||||
{
|
||||
name: "ok/ip",
|
||||
args: args{
|
||||
az: &acme.Authorization{
|
||||
Identifier: acme.Identifier{Type: "ip", Value: "192.168.42.42"},
|
||||
Wildcard: false,
|
||||
},
|
||||
},
|
||||
want: []acme.ChallengeType{acme.HTTP01, acme.TLSALPN01},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := challengeTypes(tt.args.az); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Handler.challengeTypes() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,13 +21,21 @@ import (
|
|||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
type ChallengeType string
|
||||
|
||||
const (
|
||||
HTTP01 ChallengeType = "http-01"
|
||||
DNS01 ChallengeType = "dns-01"
|
||||
TLSALPN01 ChallengeType = "tls-alpn-01"
|
||||
)
|
||||
|
||||
// Challenge represents an ACME response Challenge type.
|
||||
type Challenge struct {
|
||||
ID string `json:"-"`
|
||||
AccountID string `json:"-"`
|
||||
AuthorizationID string `json:"-"`
|
||||
Value string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Type ChallengeType `json:"type"`
|
||||
Status Status `json:"status"`
|
||||
Token string `json:"token"`
|
||||
ValidatedAt string `json:"validated,omitempty"`
|
||||
|
@ -54,11 +62,11 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey,
|
|||
return nil
|
||||
}
|
||||
switch ch.Type {
|
||||
case "http-01":
|
||||
case HTTP01:
|
||||
return http01Validate(ctx, ch, db, jwk, vo)
|
||||
case "dns-01":
|
||||
case DNS01:
|
||||
return dns01Validate(ctx, ch, db, jwk, vo)
|
||||
case "tls-alpn-01":
|
||||
case TLSALPN01:
|
||||
return tlsalpn01Validate(ctx, ch, db, jwk, vo)
|
||||
default:
|
||||
return NewErrorISE("unexpected challenge type '%s'", ch.Type)
|
||||
|
@ -113,7 +121,7 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
|
|||
// ACME servers that implement "acme-tls/1" MUST only negotiate TLS 1.2
|
||||
// [RFC5246] or higher when connecting to clients for validation.
|
||||
MinVersion: tls.VersionTLS12,
|
||||
ServerName: ch.Value,
|
||||
ServerName: serverName(ch),
|
||||
InsecureSkipVerify: true, // we expect a self-signed challenge certificate
|
||||
}
|
||||
|
||||
|
@ -141,9 +149,17 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
|
|||
|
||||
leafCert := certs[0]
|
||||
|
||||
// if no DNS names present, look for IP address and verify that exactly one exists
|
||||
if len(leafCert.DNSNames) == 0 {
|
||||
if len(leafCert.IPAddresses) != 1 || !leafCert.IPAddresses[0].Equal(net.ParseIP(ch.Value)) {
|
||||
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
|
||||
"incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value))
|
||||
}
|
||||
} else {
|
||||
if len(leafCert.DNSNames) != 1 || !strings.EqualFold(leafCert.DNSNames[0], ch.Value) {
|
||||
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType,
|
||||
"incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value))
|
||||
"incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value))
|
||||
}
|
||||
}
|
||||
|
||||
idPeAcmeIdentifier := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||
|
@ -244,6 +260,65 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK
|
|||
return nil
|
||||
}
|
||||
|
||||
// serverName determines the SNI HostName to set based on an acme.Challenge
|
||||
// for TLS-ALPN-01 challenges RFC8738 states that, if HostName is an IP, it
|
||||
// should be the ARPA address https://datatracker.ietf.org/doc/html/rfc8738#section-6.
|
||||
// It also references TLS Extensions [RFC6066].
|
||||
func serverName(ch *Challenge) string {
|
||||
var serverName string
|
||||
ip := net.ParseIP(ch.Value)
|
||||
if ip != nil {
|
||||
serverName = reverseAddr(ip)
|
||||
} else {
|
||||
serverName = ch.Value
|
||||
}
|
||||
return serverName
|
||||
}
|
||||
|
||||
// reverseaddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
|
||||
// address addr suitable for rDNS (PTR) record lookup or an error if it fails
|
||||
// to parse the IP address.
|
||||
// Implementation taken and adapted from https://golang.org/src/net/dnsclient.go?s=780:834#L20
|
||||
func reverseAddr(ip net.IP) (arpa string) {
|
||||
if ip.To4() != nil {
|
||||
return uitoa(uint(ip[15])) + "." + uitoa(uint(ip[14])) + "." + uitoa(uint(ip[13])) + "." + uitoa(uint(ip[12])) + ".in-addr.arpa."
|
||||
}
|
||||
// Must be IPv6
|
||||
buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
|
||||
// Add it, in reverse, to the buffer
|
||||
for i := len(ip) - 1; i >= 0; i-- {
|
||||
v := ip[i]
|
||||
buf = append(buf, hexit[v&0xF],
|
||||
'.',
|
||||
hexit[v>>4],
|
||||
'.')
|
||||
}
|
||||
// Append "ip6.arpa." and return (buf already has the final .)
|
||||
buf = append(buf, "ip6.arpa."...)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// Convert unsigned integer to decimal string.
|
||||
// Implementation taken from https://golang.org/src/net/parse.go
|
||||
func uitoa(val uint) string {
|
||||
if val == 0 { // avoid string allocation
|
||||
return "0"
|
||||
}
|
||||
var buf [20]byte // big enough for 64bit value base 10
|
||||
i := len(buf) - 1
|
||||
for val >= 10 {
|
||||
q := val / 10
|
||||
buf[i] = byte('0' + val - q*10)
|
||||
i--
|
||||
val = q
|
||||
}
|
||||
// val < 10
|
||||
buf[i] = byte('0' + val)
|
||||
return string(buf[i:])
|
||||
}
|
||||
|
||||
const hexit = "0123456789abcdef"
|
||||
|
||||
// KeyAuthorization creates the ACME key authorization value from a token
|
||||
// and a jwk.
|
||||
func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) {
|
||||
|
|
|
@ -1544,7 +1544,7 @@ func TestTLSALPN01Validate(t *testing.T) {
|
|||
err: NewErrorISE("failure saving error to acme challenge: force"),
|
||||
}
|
||||
},
|
||||
"ok/no-names-error": func(t *testing.T) test {
|
||||
"ok/no-names-nor-ips-error": func(t *testing.T) test {
|
||||
ch := makeTLSCh()
|
||||
|
||||
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||
|
@ -1573,7 +1573,7 @@ func TestTLSALPN01Validate(t *testing.T) {
|
|||
assert.Equals(t, updch.Type, ch.Type)
|
||||
assert.Equals(t, updch.Value, ch.Value)
|
||||
|
||||
err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value)
|
||||
err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value)
|
||||
|
||||
assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
|
||||
assert.Equals(t, updch.Error.Type, err.Type)
|
||||
|
@ -1616,7 +1616,7 @@ func TestTLSALPN01Validate(t *testing.T) {
|
|||
assert.Equals(t, updch.Type, ch.Type)
|
||||
assert.Equals(t, updch.Value, ch.Value)
|
||||
|
||||
err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value)
|
||||
err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value)
|
||||
|
||||
assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
|
||||
assert.Equals(t, updch.Error.Type, err.Type)
|
||||
|
@ -1660,7 +1660,7 @@ func TestTLSALPN01Validate(t *testing.T) {
|
|||
assert.Equals(t, updch.Type, ch.Type)
|
||||
assert.Equals(t, updch.Value, ch.Value)
|
||||
|
||||
err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value)
|
||||
err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value)
|
||||
|
||||
assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
|
||||
assert.Equals(t, updch.Error.Type, err.Type)
|
||||
|
@ -1703,7 +1703,7 @@ func TestTLSALPN01Validate(t *testing.T) {
|
|||
assert.Equals(t, updch.Type, ch.Type)
|
||||
assert.Equals(t, updch.Value, ch.Value)
|
||||
|
||||
err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value)
|
||||
err := NewError(ErrorRejectedIdentifierType, "incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single IP address or DNS name, %v", ch.Value)
|
||||
|
||||
assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
|
||||
assert.Equals(t, updch.Error.Type, err.Type)
|
||||
|
@ -2187,6 +2187,43 @@ func TestTLSALPN01Validate(t *testing.T) {
|
|||
srv, tlsDial := newTestTLSALPNServer(cert)
|
||||
srv.Start()
|
||||
|
||||
return test{
|
||||
ch: ch,
|
||||
vo: &ValidateChallengeOptions{
|
||||
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, StatusValid)
|
||||
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/ip": func(t *testing.T) test {
|
||||
ch := makeTLSCh()
|
||||
ch.Value = "127.0.0.1"
|
||||
|
||||
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)
|
||||
|
||||
srv, tlsDial := newTestTLSALPNServer(cert)
|
||||
srv.Start()
|
||||
|
||||
return test{
|
||||
ch: ch,
|
||||
vo: &ValidateChallengeOptions{
|
||||
|
@ -2235,3 +2272,82 @@ func TestTLSALPN01Validate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_reverseAddr(t *testing.T) {
|
||||
type args struct {
|
||||
ip net.IP
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantArpa string
|
||||
}{
|
||||
{
|
||||
name: "ok/ipv4",
|
||||
args: args{
|
||||
ip: net.ParseIP("127.0.0.1"),
|
||||
},
|
||||
wantArpa: "1.0.0.127.in-addr.arpa.",
|
||||
},
|
||||
{
|
||||
name: "ok/ipv6",
|
||||
args: args{
|
||||
ip: net.ParseIP("2001:db8::567:89ab"),
|
||||
},
|
||||
wantArpa: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotArpa := reverseAddr(tt.args.ip); gotArpa != tt.wantArpa {
|
||||
t.Errorf("reverseAddr() = %v, want %v", gotArpa, tt.wantArpa)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_serverName(t *testing.T) {
|
||||
type args struct {
|
||||
ch *Challenge
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "ok/dns",
|
||||
args: args{
|
||||
ch: &Challenge{
|
||||
Value: "example.com",
|
||||
},
|
||||
},
|
||||
want: "example.com",
|
||||
},
|
||||
{
|
||||
name: "ok/ipv4",
|
||||
args: args{
|
||||
ch: &Challenge{
|
||||
Value: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
want: "1.0.0.127.in-addr.arpa.",
|
||||
},
|
||||
{
|
||||
name: "ok/ipv6",
|
||||
args: args{
|
||||
ch: &Challenge{
|
||||
Value: "2001:db8::567:89ab",
|
||||
},
|
||||
},
|
||||
want: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := serverName(tt.args.ch); got != tt.want {
|
||||
t.Errorf("serverName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
type CertificateAuthority interface {
|
||||
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
Revoke(context.Context, *authority.RevokeOptions) error
|
||||
LoadProvisionerByID(string) (provisioner.Interface, error)
|
||||
LoadProvisionerByName(string) (provisioner.Interface, error)
|
||||
}
|
||||
|
||||
// Clock that returns time in UTC rounded to seconds.
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
type dbChallenge struct {
|
||||
ID string `json:"id"`
|
||||
AccountID string `json:"accountID"`
|
||||
Type string `json:"type"`
|
||||
Type acme.ChallengeType `json:"type"`
|
||||
Status acme.Status `json:"status"`
|
||||
Token string `json:"token"`
|
||||
Value string `json:"value"`
|
||||
|
|
187
acme/order.go
187
acme/order.go
|
@ -1,9 +1,11 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -12,9 +14,16 @@ import (
|
|||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
type IdentifierType string
|
||||
|
||||
const (
|
||||
IP IdentifierType = "ip"
|
||||
DNS IdentifierType = "dns"
|
||||
)
|
||||
|
||||
// Identifier encodes the type that an order pertains to.
|
||||
type Identifier struct {
|
||||
Type string `json:"type"`
|
||||
Type IdentifierType `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
|
@ -131,41 +140,13 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
|
|||
return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID)
|
||||
}
|
||||
|
||||
// RFC8555: The CSR MUST indicate the exact same set of requested
|
||||
// identifiers as the initial newOrder request. Identifiers of type "dns"
|
||||
// MUST appear either in the commonName portion of the requested subject
|
||||
// name or in an extensionRequest attribute [RFC2985] requesting a
|
||||
// subjectAltName extension, or both.
|
||||
if csr.Subject.CommonName != "" {
|
||||
csr.DNSNames = append(csr.DNSNames, csr.Subject.CommonName)
|
||||
}
|
||||
csr.DNSNames = uniqueSortedLowerNames(csr.DNSNames)
|
||||
orderNames := make([]string, len(o.Identifiers))
|
||||
for i, n := range o.Identifiers {
|
||||
orderNames[i] = n.Value
|
||||
}
|
||||
orderNames = uniqueSortedLowerNames(orderNames)
|
||||
// canonicalize the CSR to allow for comparison
|
||||
csr = canonicalize(csr)
|
||||
|
||||
// Validate identifier names against CSR alternative names.
|
||||
//
|
||||
// Note that with certificate templates we are not going to check for the
|
||||
// absence of other SANs as they will only be set if the templates allows
|
||||
// them.
|
||||
if len(csr.DNSNames) != len(orderNames) {
|
||||
return NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+
|
||||
"CSR names = %v, Order names = %v", csr.DNSNames, orderNames)
|
||||
}
|
||||
|
||||
sans := make([]x509util.SubjectAlternativeName, len(csr.DNSNames))
|
||||
for i := range csr.DNSNames {
|
||||
if csr.DNSNames[i] != orderNames[i] {
|
||||
return NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+
|
||||
"CSR names = %v, Order names = %v", csr.DNSNames, orderNames)
|
||||
}
|
||||
sans[i] = x509util.SubjectAlternativeName{
|
||||
Type: x509util.DNSType,
|
||||
Value: csr.DNSNames[i],
|
||||
}
|
||||
// retrieve the requested SANs for the Order
|
||||
sans, err := o.sans(csr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get authorizations from the ACME provisioner.
|
||||
|
@ -213,6 +194,122 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
|
|||
return nil
|
||||
}
|
||||
|
||||
func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativeName, error) {
|
||||
|
||||
var sans []x509util.SubjectAlternativeName
|
||||
|
||||
// order the DNS names and IP addresses, so that they can be compared against the canonicalized CSR
|
||||
orderNames := make([]string, numberOfIdentifierType(DNS, o.Identifiers))
|
||||
orderIPs := make([]net.IP, numberOfIdentifierType(IP, o.Identifiers))
|
||||
indexDNS, indexIP := 0, 0
|
||||
for _, n := range o.Identifiers {
|
||||
switch n.Type {
|
||||
case DNS:
|
||||
orderNames[indexDNS] = n.Value
|
||||
indexDNS++
|
||||
case IP:
|
||||
orderIPs[indexIP] = net.ParseIP(n.Value) // NOTE: this assumes are all valid IPs at this time; or will result in nil entries
|
||||
indexIP++
|
||||
default:
|
||||
return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type)
|
||||
}
|
||||
}
|
||||
orderNames = uniqueSortedLowerNames(orderNames)
|
||||
orderIPs = uniqueSortedIPs(orderIPs)
|
||||
|
||||
totalNumberOfSANs := len(csr.DNSNames) + len(csr.IPAddresses)
|
||||
sans = make([]x509util.SubjectAlternativeName, totalNumberOfSANs)
|
||||
index := 0
|
||||
|
||||
// Validate identifier names against CSR alternative names.
|
||||
//
|
||||
// Note that with certificate templates we are not going to check for the
|
||||
// absence of other SANs as they will only be set if the template allows
|
||||
// them.
|
||||
if len(csr.DNSNames) != len(orderNames) {
|
||||
return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+
|
||||
"CSR names = %v, Order names = %v", csr.DNSNames, orderNames)
|
||||
}
|
||||
|
||||
for i := range csr.DNSNames {
|
||||
if csr.DNSNames[i] != orderNames[i] {
|
||||
return sans, NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+
|
||||
"CSR names = %v, Order names = %v", csr.DNSNames, orderNames)
|
||||
}
|
||||
sans[index] = x509util.SubjectAlternativeName{
|
||||
Type: x509util.DNSType,
|
||||
Value: csr.DNSNames[i],
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
if len(csr.IPAddresses) != len(orderIPs) {
|
||||
return sans, NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+
|
||||
"CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs)
|
||||
}
|
||||
|
||||
for i := range csr.IPAddresses {
|
||||
if !ipsAreEqual(csr.IPAddresses[i], orderIPs[i]) {
|
||||
return sans, NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+
|
||||
"CSR IPs = %v, Order IPs = %v", csr.IPAddresses, orderIPs)
|
||||
}
|
||||
sans[index] = x509util.SubjectAlternativeName{
|
||||
Type: x509util.IPType,
|
||||
Value: csr.IPAddresses[i].String(),
|
||||
}
|
||||
index++
|
||||
}
|
||||
|
||||
return sans, nil
|
||||
}
|
||||
|
||||
// numberOfIdentifierType returns the number of Identifiers that
|
||||
// are of type typ.
|
||||
func numberOfIdentifierType(typ IdentifierType, ids []Identifier) int {
|
||||
c := 0
|
||||
for _, id := range ids {
|
||||
if id.Type == typ {
|
||||
c++
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// canonicalize canonicalizes a CSR so that it can be compared against an Order
|
||||
// NOTE: this effectively changes the order of SANs in the CSR, which may be OK,
|
||||
// but may not be expected.
|
||||
func canonicalize(csr *x509.CertificateRequest) (canonicalized *x509.CertificateRequest) {
|
||||
|
||||
// for clarity only; we're operating on the same object by pointer
|
||||
canonicalized = csr
|
||||
|
||||
// RFC8555: The CSR MUST indicate the exact same set of requested
|
||||
// identifiers as the initial newOrder request. Identifiers of type "dns"
|
||||
// MUST appear either in the commonName portion of the requested subject
|
||||
// name or in an extensionRequest attribute [RFC2985] requesting a
|
||||
// subjectAltName extension, or both.
|
||||
if csr.Subject.CommonName != "" {
|
||||
canonicalized.DNSNames = append(csr.DNSNames, csr.Subject.CommonName)
|
||||
}
|
||||
canonicalized.DNSNames = uniqueSortedLowerNames(csr.DNSNames)
|
||||
canonicalized.IPAddresses = uniqueSortedIPs(csr.IPAddresses)
|
||||
|
||||
return canonicalized
|
||||
}
|
||||
|
||||
// ipsAreEqual compares IPs to be equal. Nil values (i.e. invalid IPs) are
|
||||
// not considered equal. IPv6 representations of IPv4 addresses are
|
||||
// considered equal to the IPv4 address in this implementation, which is
|
||||
// standard Go behavior. An example is "::ffff:192.168.42.42", which
|
||||
// is equal to "192.168.42.42". This is considered a known issue within
|
||||
// step and is tracked here too: https://github.com/golang/go/issues/37921.
|
||||
func ipsAreEqual(x, y net.IP) bool {
|
||||
if x == nil || y == nil {
|
||||
return false
|
||||
}
|
||||
return x.Equal(y)
|
||||
}
|
||||
|
||||
// uniqueSortedLowerNames returns the set of all unique names in the input after all
|
||||
// of them are lowercased. The returned names will be in their lowercased form
|
||||
// and sorted alphabetically.
|
||||
|
@ -228,3 +325,23 @@ func uniqueSortedLowerNames(names []string) (unique []string) {
|
|||
sort.Strings(unique)
|
||||
return
|
||||
}
|
||||
|
||||
// uniqueSortedIPs returns the set of all unique net.IPs in the input. They
|
||||
// are sorted by their bytes (octet) representation.
|
||||
func uniqueSortedIPs(ips []net.IP) (unique []net.IP) {
|
||||
type entry struct {
|
||||
ip net.IP
|
||||
}
|
||||
ipEntryMap := make(map[string]entry, len(ips))
|
||||
for _, ip := range ips {
|
||||
ipEntryMap[ip.String()] = entry{ip: ip}
|
||||
}
|
||||
unique = make([]net.IP, 0, len(ipEntryMap))
|
||||
for _, entry := range ipEntryMap {
|
||||
unique = append(unique, entry.ip)
|
||||
}
|
||||
sort.Slice(unique, func(i, j int) bool {
|
||||
return bytes.Compare(unique[i], unique[j]) < 0
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -12,6 +14,7 @@ import (
|
|||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"go.step.sm/crypto/x509util"
|
||||
)
|
||||
|
||||
func TestOrder_UpdateStatus(t *testing.T) {
|
||||
|
@ -263,7 +266,7 @@ func TestOrder_UpdateStatus(t *testing.T) {
|
|||
|
||||
type mockSignAuth struct {
|
||||
sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
loadProvisionerByID func(string) (provisioner.Interface, error)
|
||||
loadProvisionerByName func(string) (provisioner.Interface, error)
|
||||
ret1, ret2 interface{}
|
||||
err error
|
||||
}
|
||||
|
@ -277,9 +280,9 @@ func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.S
|
|||
return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
|
||||
}
|
||||
|
||||
func (m *mockSignAuth) LoadProvisionerByID(id string) (provisioner.Interface, error) {
|
||||
if m.loadProvisionerByID != nil {
|
||||
return m.loadProvisionerByID(id)
|
||||
func (m *mockSignAuth) LoadProvisionerByName(name string) (provisioner.Interface, error) {
|
||||
if m.loadProvisionerByName != nil {
|
||||
return m.loadProvisionerByName(name)
|
||||
}
|
||||
return m.ret1.(provisioner.Interface), m.err
|
||||
}
|
||||
|
@ -369,61 +372,6 @@ func TestOrder_Finalize(t *testing.T) {
|
|||
err: NewErrorISE("unrecognized order status: %s", o.Status),
|
||||
}
|
||||
},
|
||||
"fail/error-names-length-mismatch": func(t *testing.T) test {
|
||||
now := clock.Now()
|
||||
o := &Order{
|
||||
ID: "oID",
|
||||
AccountID: "accID",
|
||||
Status: StatusReady,
|
||||
ExpiresAt: now.Add(5 * time.Minute),
|
||||
AuthorizationIDs: []string{"a", "b"},
|
||||
Identifiers: []Identifier{
|
||||
{Type: "dns", Value: "foo.internal"},
|
||||
{Type: "dns", Value: "bar.internal"},
|
||||
},
|
||||
}
|
||||
orderNames := []string{"bar.internal", "foo.internal"}
|
||||
csr := &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "foo.internal",
|
||||
},
|
||||
}
|
||||
|
||||
return test{
|
||||
o: o,
|
||||
csr: csr,
|
||||
err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+
|
||||
"CSR names = %v, Order names = %v", []string{"foo.internal"}, orderNames),
|
||||
}
|
||||
},
|
||||
"fail/error-names-mismatch": func(t *testing.T) test {
|
||||
now := clock.Now()
|
||||
o := &Order{
|
||||
ID: "oID",
|
||||
AccountID: "accID",
|
||||
Status: StatusReady,
|
||||
ExpiresAt: now.Add(5 * time.Minute),
|
||||
AuthorizationIDs: []string{"a", "b"},
|
||||
Identifiers: []Identifier{
|
||||
{Type: "dns", Value: "foo.internal"},
|
||||
{Type: "dns", Value: "bar.internal"},
|
||||
},
|
||||
}
|
||||
orderNames := []string{"bar.internal", "foo.internal"}
|
||||
csr := &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "foo.internal",
|
||||
},
|
||||
DNSNames: []string{"zap.internal"},
|
||||
}
|
||||
|
||||
return test{
|
||||
o: o,
|
||||
csr: csr,
|
||||
err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+
|
||||
"CSR names = %v, Order names = %v", []string{"foo.internal", "zap.internal"}, orderNames),
|
||||
}
|
||||
},
|
||||
"fail/error-provisioner-auth": func(t *testing.T) test {
|
||||
now := clock.Now()
|
||||
o := &Order{
|
||||
|
@ -655,7 +603,7 @@ func TestOrder_Finalize(t *testing.T) {
|
|||
err: NewErrorISE("error updating order oID: force"),
|
||||
}
|
||||
},
|
||||
"ok/new-cert": func(t *testing.T) test {
|
||||
"ok/new-cert-dns": func(t *testing.T) test {
|
||||
now := clock.Now()
|
||||
o := &Order{
|
||||
ID: "oID",
|
||||
|
@ -679,6 +627,131 @@ func TestOrder_Finalize(t *testing.T) {
|
|||
bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}}
|
||||
baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}}
|
||||
|
||||
return test{
|
||||
o: o,
|
||||
csr: csr,
|
||||
prov: &MockProvisioner{
|
||||
MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) {
|
||||
assert.Equals(t, token, "")
|
||||
return nil, nil
|
||||
},
|
||||
MgetOptions: func() *provisioner.Options {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
ca: &mockSignAuth{
|
||||
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
|
||||
assert.Equals(t, _csr, csr)
|
||||
return []*x509.Certificate{foo, bar, baz}, nil
|
||||
},
|
||||
},
|
||||
db: &MockDB{
|
||||
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
|
||||
cert.ID = "certID"
|
||||
assert.Equals(t, cert.AccountID, o.AccountID)
|
||||
assert.Equals(t, cert.OrderID, o.ID)
|
||||
assert.Equals(t, cert.Leaf, foo)
|
||||
assert.Equals(t, cert.Intermediates, []*x509.Certificate{bar, baz})
|
||||
return nil
|
||||
},
|
||||
MockUpdateOrder: func(ctx context.Context, updo *Order) error {
|
||||
assert.Equals(t, updo.CertificateID, "certID")
|
||||
assert.Equals(t, updo.Status, StatusValid)
|
||||
assert.Equals(t, updo.ID, o.ID)
|
||||
assert.Equals(t, updo.AccountID, o.AccountID)
|
||||
assert.Equals(t, updo.ExpiresAt, o.ExpiresAt)
|
||||
assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs)
|
||||
assert.Equals(t, updo.Identifiers, o.Identifiers)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/new-cert-ip": func(t *testing.T) test {
|
||||
now := clock.Now()
|
||||
o := &Order{
|
||||
ID: "oID",
|
||||
AccountID: "accID",
|
||||
Status: StatusReady,
|
||||
ExpiresAt: now.Add(5 * time.Minute),
|
||||
AuthorizationIDs: []string{"a", "b"},
|
||||
Identifiers: []Identifier{
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
{Type: "ip", Value: "192.168.43.42"},
|
||||
},
|
||||
}
|
||||
csr := &x509.CertificateRequest{
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")}, // in case of IPs, no Common Name
|
||||
}
|
||||
|
||||
foo := &x509.Certificate{Subject: pkix.Name{CommonName: "foo"}}
|
||||
bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}}
|
||||
baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}}
|
||||
|
||||
return test{
|
||||
o: o,
|
||||
csr: csr,
|
||||
prov: &MockProvisioner{
|
||||
MauthorizeSign: func(ctx context.Context, token string) ([]provisioner.SignOption, error) {
|
||||
assert.Equals(t, token, "")
|
||||
return nil, nil
|
||||
},
|
||||
MgetOptions: func() *provisioner.Options {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
ca: &mockSignAuth{
|
||||
sign: func(_csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
|
||||
assert.Equals(t, _csr, csr)
|
||||
return []*x509.Certificate{foo, bar, baz}, nil
|
||||
},
|
||||
},
|
||||
db: &MockDB{
|
||||
MockCreateCertificate: func(ctx context.Context, cert *Certificate) error {
|
||||
cert.ID = "certID"
|
||||
assert.Equals(t, cert.AccountID, o.AccountID)
|
||||
assert.Equals(t, cert.OrderID, o.ID)
|
||||
assert.Equals(t, cert.Leaf, foo)
|
||||
assert.Equals(t, cert.Intermediates, []*x509.Certificate{bar, baz})
|
||||
return nil
|
||||
},
|
||||
MockUpdateOrder: func(ctx context.Context, updo *Order) error {
|
||||
assert.Equals(t, updo.CertificateID, "certID")
|
||||
assert.Equals(t, updo.Status, StatusValid)
|
||||
assert.Equals(t, updo.ID, o.ID)
|
||||
assert.Equals(t, updo.AccountID, o.AccountID)
|
||||
assert.Equals(t, updo.ExpiresAt, o.ExpiresAt)
|
||||
assert.Equals(t, updo.AuthorizationIDs, o.AuthorizationIDs)
|
||||
assert.Equals(t, updo.Identifiers, o.Identifiers)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/new-cert-dns-and-ip": func(t *testing.T) test {
|
||||
now := clock.Now()
|
||||
o := &Order{
|
||||
ID: "oID",
|
||||
AccountID: "accID",
|
||||
Status: StatusReady,
|
||||
ExpiresAt: now.Add(5 * time.Minute),
|
||||
AuthorizationIDs: []string{"a", "b"},
|
||||
Identifiers: []Identifier{
|
||||
{Type: "dns", Value: "foo.internal"},
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
},
|
||||
}
|
||||
csr := &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "foo.internal",
|
||||
},
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.42.42")},
|
||||
}
|
||||
|
||||
foo := &x509.Certificate{Subject: pkix.Name{CommonName: "foo"}}
|
||||
bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}}
|
||||
baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}}
|
||||
|
||||
return test{
|
||||
o: o,
|
||||
csr: csr,
|
||||
|
@ -742,3 +815,592 @@ func TestOrder_Finalize(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_uniqueSortedIPs(t *testing.T) {
|
||||
type args struct {
|
||||
ips []net.IP
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantUnique []net.IP
|
||||
}{
|
||||
{
|
||||
name: "ok/empty",
|
||||
args: args{
|
||||
ips: []net.IP{},
|
||||
},
|
||||
wantUnique: []net.IP{},
|
||||
},
|
||||
{
|
||||
name: "ok/single-ipv4",
|
||||
args: args{
|
||||
ips: []net.IP{net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
wantUnique: []net.IP{net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
{
|
||||
name: "ok/multiple-ipv4",
|
||||
args: args{
|
||||
ips: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.10"), net.ParseIP("192.168.42.1")},
|
||||
},
|
||||
wantUnique: []net.IP{net.ParseIP("192.168.42.1"), net.ParseIP("192.168.42.10"), net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
{
|
||||
name: "ok/unique-ipv4",
|
||||
args: args{
|
||||
ips: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
wantUnique: []net.IP{net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
{
|
||||
name: "ok/single-ipv6",
|
||||
args: args{
|
||||
ips: []net.IP{net.ParseIP("2001:db8::30")},
|
||||
},
|
||||
wantUnique: []net.IP{net.ParseIP("2001:db8::30")},
|
||||
},
|
||||
{
|
||||
name: "ok/multiple-ipv6",
|
||||
args: args{
|
||||
ips: []net.IP{net.ParseIP("2001:db8::30"), net.ParseIP("2001:db8::20"), net.ParseIP("2001:db8::10")},
|
||||
},
|
||||
wantUnique: []net.IP{net.ParseIP("2001:db8::10"), net.ParseIP("2001:db8::20"), net.ParseIP("2001:db8::30")},
|
||||
},
|
||||
{
|
||||
name: "ok/unique-ipv6",
|
||||
args: args{
|
||||
ips: []net.IP{net.ParseIP("2001:db8::1"), net.ParseIP("2001:db8::1")},
|
||||
},
|
||||
wantUnique: []net.IP{net.ParseIP("2001:db8::1")},
|
||||
},
|
||||
{
|
||||
name: "ok/mixed-ipv4-and-ipv6",
|
||||
args: args{
|
||||
ips: []net.IP{net.ParseIP("2001:db8::1"), net.ParseIP("2001:db8::1"), net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
wantUnique: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("2001:db8::1")},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotUnique := uniqueSortedIPs(tt.args.ips); !reflect.DeepEqual(gotUnique, tt.wantUnique) {
|
||||
t.Errorf("uniqueSortedIPs() = %v, want %v", gotUnique, tt.wantUnique)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_numberOfIdentifierType(t *testing.T) {
|
||||
type args struct {
|
||||
typ IdentifierType
|
||||
ids []Identifier
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "ok/no-identifiers",
|
||||
args: args{
|
||||
typ: DNS,
|
||||
ids: []Identifier{},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "ok/no-dns",
|
||||
args: args{
|
||||
typ: DNS,
|
||||
ids: []Identifier{
|
||||
{
|
||||
Type: IP,
|
||||
Value: "192.168.42.42",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "ok/no-ips",
|
||||
args: args{
|
||||
typ: IP,
|
||||
ids: []Identifier{
|
||||
{
|
||||
Type: DNS,
|
||||
Value: "example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "ok/one-dns",
|
||||
args: args{
|
||||
typ: DNS,
|
||||
ids: []Identifier{
|
||||
{
|
||||
Type: DNS,
|
||||
Value: "example.com",
|
||||
},
|
||||
{
|
||||
Type: IP,
|
||||
Value: "192.168.42.42",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "ok/one-ip",
|
||||
args: args{
|
||||
typ: IP,
|
||||
ids: []Identifier{
|
||||
{
|
||||
Type: DNS,
|
||||
Value: "example.com",
|
||||
},
|
||||
{
|
||||
Type: IP,
|
||||
Value: "192.168.42.42",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "ok/more-dns",
|
||||
args: args{
|
||||
typ: DNS,
|
||||
ids: []Identifier{
|
||||
{
|
||||
Type: DNS,
|
||||
Value: "example.com",
|
||||
},
|
||||
{
|
||||
Type: DNS,
|
||||
Value: "*.example.com",
|
||||
},
|
||||
{
|
||||
Type: IP,
|
||||
Value: "192.168.42.42",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "ok/more-ips",
|
||||
args: args{
|
||||
typ: IP,
|
||||
ids: []Identifier{
|
||||
{
|
||||
Type: DNS,
|
||||
Value: "example.com",
|
||||
},
|
||||
{
|
||||
Type: IP,
|
||||
Value: "192.168.42.42",
|
||||
},
|
||||
{
|
||||
Type: IP,
|
||||
Value: "192.168.42.43",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: 2,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := numberOfIdentifierType(tt.args.typ, tt.args.ids); got != tt.want {
|
||||
t.Errorf("numberOfIdentifierType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ipsAreEqual(t *testing.T) {
|
||||
type args struct {
|
||||
x net.IP
|
||||
y net.IP
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "ok/ipv4",
|
||||
args: args{
|
||||
x: net.ParseIP("192.168.42.42"),
|
||||
y: net.ParseIP("192.168.42.42"),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "fail/ipv4",
|
||||
args: args{
|
||||
x: net.ParseIP("192.168.42.42"),
|
||||
y: net.ParseIP("192.168.42.43"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "ok/ipv6",
|
||||
args: args{
|
||||
x: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "fail/ipv6",
|
||||
args: args{
|
||||
x: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7335"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "fail/ipv4-and-ipv6",
|
||||
args: args{
|
||||
x: net.ParseIP("192.168.42.42"),
|
||||
y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "ok/ipv4-mapped-to-ipv6",
|
||||
args: args{
|
||||
x: net.ParseIP("192.168.42.42"),
|
||||
y: net.ParseIP("::ffff:192.168.42.42"), // parsed to the same IPv4 by Go
|
||||
},
|
||||
want: true, // we expect this to happen; a known issue in which ipv4 mapped ipv6 addresses are considered the same as their ipv4 counterpart
|
||||
},
|
||||
{
|
||||
name: "fail/invalid-ipv4-and-valid-ipv6",
|
||||
args: args{
|
||||
x: net.ParseIP("192.168.42.1000"),
|
||||
y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "fail/valid-ipv4-and-invalid-ipv6",
|
||||
args: args{
|
||||
x: net.ParseIP("192.168.42.42"),
|
||||
y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:733400"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "fail/invalid-ipv4-and-invalid-ipv6",
|
||||
args: args{
|
||||
x: net.ParseIP("192.168.42.1000"),
|
||||
y: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:1000000"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ipsAreEqual(tt.args.x, tt.args.y); got != tt.want {
|
||||
t.Errorf("ipsAreEqual() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_canonicalize(t *testing.T) {
|
||||
type args struct {
|
||||
csr *x509.CertificateRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantCanonicalized *x509.CertificateRequest
|
||||
}{
|
||||
{
|
||||
name: "ok/dns",
|
||||
args: args{
|
||||
csr: &x509.CertificateRequest{
|
||||
DNSNames: []string{"www.example.com", "example.com"},
|
||||
},
|
||||
},
|
||||
wantCanonicalized: &x509.CertificateRequest{
|
||||
DNSNames: []string{"example.com", "www.example.com"},
|
||||
IPAddresses: []net.IP{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok/common-name",
|
||||
args: args{
|
||||
csr: &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "example.com",
|
||||
},
|
||||
DNSNames: []string{"www.example.com"},
|
||||
},
|
||||
},
|
||||
wantCanonicalized: &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "example.com",
|
||||
},
|
||||
DNSNames: []string{"example.com", "www.example.com"},
|
||||
IPAddresses: []net.IP{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok/ipv4",
|
||||
args: args{
|
||||
csr: &x509.CertificateRequest{
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
},
|
||||
wantCanonicalized: &x509.CertificateRequest{
|
||||
DNSNames: []string{},
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok/mixed",
|
||||
args: args{
|
||||
csr: &x509.CertificateRequest{
|
||||
DNSNames: []string{"www.example.com", "example.com"},
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
},
|
||||
wantCanonicalized: &x509.CertificateRequest{
|
||||
DNSNames: []string{"example.com", "www.example.com"},
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok/mixed-common-name",
|
||||
args: args{
|
||||
csr: &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "example.com",
|
||||
},
|
||||
DNSNames: []string{"www.example.com"},
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
},
|
||||
wantCanonicalized: &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "example.com",
|
||||
},
|
||||
DNSNames: []string{"example.com", "www.example.com"},
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotCanonicalized := canonicalize(tt.args.csr); !reflect.DeepEqual(gotCanonicalized, tt.wantCanonicalized) {
|
||||
t.Errorf("canonicalize() = %v, want %v", gotCanonicalized, tt.wantCanonicalized)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrder_sans(t *testing.T) {
|
||||
type fields struct {
|
||||
Identifiers []Identifier
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
csr *x509.CertificateRequest
|
||||
want []x509util.SubjectAlternativeName
|
||||
err *Error
|
||||
}{
|
||||
{
|
||||
name: "ok/dns",
|
||||
fields: fields{
|
||||
Identifiers: []Identifier{
|
||||
{Type: "dns", Value: "example.com"},
|
||||
},
|
||||
},
|
||||
csr: &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "example.com",
|
||||
},
|
||||
},
|
||||
want: []x509util.SubjectAlternativeName{
|
||||
{Type: "dns", Value: "example.com"},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "fail/error-names-length-mismatch",
|
||||
fields: fields{
|
||||
Identifiers: []Identifier{
|
||||
{Type: "dns", Value: "foo.internal"},
|
||||
{Type: "dns", Value: "bar.internal"},
|
||||
},
|
||||
},
|
||||
csr: &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "foo.internal",
|
||||
},
|
||||
},
|
||||
want: []x509util.SubjectAlternativeName{},
|
||||
err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+
|
||||
"CSR names = %v, Order names = %v", []string{"foo.internal"}, []string{"bar.internal", "foo.internal"}),
|
||||
},
|
||||
{
|
||||
name: "fail/error-names-mismatch",
|
||||
fields: fields{
|
||||
Identifiers: []Identifier{
|
||||
{Type: "dns", Value: "foo.internal"},
|
||||
{Type: "dns", Value: "bar.internal"},
|
||||
},
|
||||
},
|
||||
csr: &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "foo.internal",
|
||||
},
|
||||
DNSNames: []string{"zap.internal"},
|
||||
},
|
||||
want: []x509util.SubjectAlternativeName{},
|
||||
err: NewError(ErrorBadCSRType, "CSR names do not match identifiers exactly: "+
|
||||
"CSR names = %v, Order names = %v", []string{"foo.internal", "zap.internal"}, []string{"bar.internal", "foo.internal"}),
|
||||
},
|
||||
{
|
||||
name: "ok/ipv4",
|
||||
fields: fields{
|
||||
Identifiers: []Identifier{
|
||||
{Type: "ip", Value: "192.168.43.42"},
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
},
|
||||
},
|
||||
csr: &x509.CertificateRequest{
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
want: []x509util.SubjectAlternativeName{
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
{Type: "ip", Value: "192.168.43.42"},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "ok/ipv6",
|
||||
fields: fields{
|
||||
Identifiers: []Identifier{
|
||||
{Type: "ip", Value: "2001:0db8:85a3::8a2e:0370:7335"},
|
||||
{Type: "ip", Value: "2001:0db8:85a3::8a2e:0370:7334"},
|
||||
},
|
||||
},
|
||||
csr: &x509.CertificateRequest{
|
||||
IPAddresses: []net.IP{net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7335"), net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
||||
},
|
||||
want: []x509util.SubjectAlternativeName{
|
||||
{Type: "ip", Value: "2001:db8:85a3::8a2e:370:7334"},
|
||||
{Type: "ip", Value: "2001:db8:85a3::8a2e:370:7335"},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "fail/error-ips-length-mismatch",
|
||||
fields: fields{
|
||||
Identifiers: []Identifier{
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
{Type: "ip", Value: "192.168.43.42"},
|
||||
},
|
||||
},
|
||||
csr: &x509.CertificateRequest{
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
want: []x509util.SubjectAlternativeName{},
|
||||
err: NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+
|
||||
"CSR IPs = %v, Order IPs = %v", []net.IP{net.ParseIP("192.168.42.42")}, []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")}),
|
||||
},
|
||||
{
|
||||
name: "fail/error-ips-mismatch",
|
||||
fields: fields{
|
||||
Identifiers: []Identifier{
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
{Type: "ip", Value: "192.168.43.42"},
|
||||
},
|
||||
},
|
||||
csr: &x509.CertificateRequest{
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.42.32")},
|
||||
},
|
||||
want: []x509util.SubjectAlternativeName{},
|
||||
err: NewError(ErrorBadCSRType, "CSR IPs do not match identifiers exactly: "+
|
||||
"CSR IPs = %v, Order IPs = %v", []net.IP{net.ParseIP("192.168.42.32"), net.ParseIP("192.168.42.42")}, []net.IP{net.ParseIP("192.168.42.42"), net.ParseIP("192.168.43.42")}),
|
||||
},
|
||||
{
|
||||
name: "ok/mixed",
|
||||
fields: fields{
|
||||
Identifiers: []Identifier{
|
||||
{Type: "dns", Value: "foo.internal"},
|
||||
{Type: "dns", Value: "bar.internal"},
|
||||
{Type: "ip", Value: "192.168.43.42"},
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
{Type: "ip", Value: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"},
|
||||
},
|
||||
},
|
||||
csr: &x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "bar.internal",
|
||||
},
|
||||
DNSNames: []string{"foo.internal"},
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.43.42"), net.ParseIP("192.168.42.42"), net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
||||
},
|
||||
want: []x509util.SubjectAlternativeName{
|
||||
{Type: "dns", Value: "bar.internal"},
|
||||
{Type: "dns", Value: "foo.internal"},
|
||||
{Type: "ip", Value: "192.168.42.42"},
|
||||
{Type: "ip", Value: "192.168.43.42"},
|
||||
{Type: "ip", Value: "2001:db8:85a3::8a2e:370:7334"},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "fail/unsupported-identifier-type",
|
||||
fields: fields{
|
||||
Identifiers: []Identifier{
|
||||
{Type: "ipv4", Value: "192.168.42.42"},
|
||||
},
|
||||
},
|
||||
csr: &x509.CertificateRequest{
|
||||
IPAddresses: []net.IP{net.ParseIP("192.168.42.42")},
|
||||
},
|
||||
want: []x509util.SubjectAlternativeName{},
|
||||
err: NewError(ErrorServerInternalType, "unsupported identifier type in order: ipv4"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &Order{
|
||||
Identifiers: tt.fields.Identifiers,
|
||||
}
|
||||
canonicalizedCSR := canonicalize(tt.csr)
|
||||
got, err := o.sans(canonicalizedCSR)
|
||||
if tt.err != nil {
|
||||
if err == nil {
|
||||
t.Errorf("Order.sans() = %v, want error; got none", got)
|
||||
return
|
||||
}
|
||||
switch k := err.(type) {
|
||||
case *Error:
|
||||
assert.Equals(t, k.Type, tt.err.Type)
|
||||
assert.Equals(t, k.Detail, tt.err.Detail)
|
||||
assert.Equals(t, k.Status, tt.err.Status)
|
||||
assert.Equals(t, k.Err.Error(), tt.err.Err.Error())
|
||||
assert.Equals(t, k.Detail, tt.err.Detail)
|
||||
default:
|
||||
assert.FatalError(t, errors.New("unexpected error type"))
|
||||
}
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Order.sans() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
10
api/api.go
10
api/api.go
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/go-chi/chi"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
|
@ -32,13 +33,13 @@ type Authority interface {
|
|||
// context specifies the Authorize[Sign|Revoke|etc.] method.
|
||||
Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error)
|
||||
AuthorizeSign(ott string) ([]provisioner.SignOption, error)
|
||||
GetTLSOptions() *authority.TLSOptions
|
||||
GetTLSOptions() *config.TLSOptions
|
||||
Root(shasum string) (*x509.Certificate, error)
|
||||
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||
Renew(peer *x509.Certificate) ([]*x509.Certificate, error)
|
||||
Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
|
||||
LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error)
|
||||
LoadProvisionerByID(string) (provisioner.Interface, error)
|
||||
LoadProvisionerByName(string) (provisioner.Interface, error)
|
||||
GetProvisioners(cursor string, limit int) (provisioner.List, string, error)
|
||||
Revoke(context.Context, *authority.RevokeOptions) error
|
||||
GetEncryptedKey(kid string) (string, error)
|
||||
|
@ -315,7 +316,7 @@ func certChainToPEM(certChain []*x509.Certificate) []Certificate {
|
|||
|
||||
// Provisioners returns the list of provisioners configured in the authority.
|
||||
func (h *caHandler) Provisioners(w http.ResponseWriter, r *http.Request) {
|
||||
cursor, limit, err := parseCursor(r)
|
||||
cursor, limit, err := ParseCursor(r)
|
||||
if err != nil {
|
||||
WriteError(w, errs.BadRequestErr(err))
|
||||
return
|
||||
|
@ -426,7 +427,8 @@ func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) {
|
|||
}
|
||||
}
|
||||
|
||||
func parseCursor(r *http.Request) (cursor string, limit int, err error) {
|
||||
// ParseCursor parses the cursor and limit from the request query params.
|
||||
func ParseCursor(r *http.Request) (cursor string, limit int, err error) {
|
||||
q := r.URL.Query()
|
||||
cursor = q.Get("cursor")
|
||||
if v := q.Get("limit"); len(v) > 0 {
|
||||
|
|
|
@ -430,6 +430,7 @@ type mockProvisioner struct {
|
|||
ret1, ret2, ret3 interface{}
|
||||
err error
|
||||
getID func() string
|
||||
getIDForToken func() string
|
||||
getTokenID func(string) (string, error)
|
||||
getName func() string
|
||||
getType func() provisioner.Type
|
||||
|
@ -452,6 +453,13 @@ func (m *mockProvisioner) GetID() string {
|
|||
return m.ret1.(string)
|
||||
}
|
||||
|
||||
func (m *mockProvisioner) GetIDForToken() string {
|
||||
if m.getIDForToken != nil {
|
||||
return m.getIDForToken()
|
||||
}
|
||||
return m.ret1.(string)
|
||||
}
|
||||
|
||||
func (m *mockProvisioner) GetTokenID(token string) (string, error) {
|
||||
if m.getTokenID != nil {
|
||||
return m.getTokenID(token)
|
||||
|
@ -553,7 +561,7 @@ type mockAuthority struct {
|
|||
renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
|
||||
rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
|
||||
loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, error)
|
||||
loadProvisionerByID func(provID string) (provisioner.Interface, error)
|
||||
loadProvisionerByName func(name string) (provisioner.Interface, error)
|
||||
getProvisioners func(nextCursor string, limit int) (provisioner.List, string, error)
|
||||
revoke func(context.Context, *authority.RevokeOptions) error
|
||||
getEncryptedKey func(kid string) (string, error)
|
||||
|
@ -633,9 +641,9 @@ func (m *mockAuthority) LoadProvisionerByCertificate(cert *x509.Certificate) (pr
|
|||
return m.ret1.(provisioner.Interface), m.err
|
||||
}
|
||||
|
||||
func (m *mockAuthority) LoadProvisionerByID(provID string) (provisioner.Interface, error) {
|
||||
if m.loadProvisionerByID != nil {
|
||||
return m.loadProvisionerByID(provID)
|
||||
func (m *mockAuthority) LoadProvisionerByName(name string) (provisioner.Interface, error) {
|
||||
if m.loadProvisionerByName != nil {
|
||||
return m.loadProvisionerByName(name)
|
||||
}
|
||||
return m.ret1.(provisioner.Interface), m.err
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/acme"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
"github.com/smallstep/certificates/scep"
|
||||
|
@ -19,6 +20,9 @@ func WriteError(w http.ResponseWriter, err error) {
|
|||
case *acme.Error:
|
||||
acme.WriteError(w, k)
|
||||
return
|
||||
case *admin.Error:
|
||||
admin.WriteError(w, k)
|
||||
return
|
||||
case *scep.Error:
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
default:
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
)
|
||||
|
@ -40,7 +40,7 @@ type SignResponse struct {
|
|||
ServerPEM Certificate `json:"crt"`
|
||||
CaPEM Certificate `json:"ca"`
|
||||
CertChainPEM []Certificate `json:"certChain"`
|
||||
TLSOptions *authority.TLSOptions `json:"tlsOptions,omitempty"`
|
||||
TLSOptions *config.TLSOptions `json:"tlsOptions,omitempty"`
|
||||
TLS *tls.ConnectionState `json:"-"`
|
||||
}
|
||||
|
||||
|
|
13
api/ssh.go
13
api/ssh.go
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
|
@ -22,12 +23,12 @@ type SSHAuthority interface {
|
|||
RenewSSH(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error)
|
||||
RekeySSH(ctx context.Context, cert *ssh.Certificate, key ssh.PublicKey, signOpts ...provisioner.SignOption) (*ssh.Certificate, error)
|
||||
SignSSHAddUser(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
|
||||
GetSSHRoots(ctx context.Context) (*authority.SSHKeys, error)
|
||||
GetSSHFederation(ctx context.Context) (*authority.SSHKeys, error)
|
||||
GetSSHRoots(ctx context.Context) (*config.SSHKeys, error)
|
||||
GetSSHFederation(ctx context.Context) (*config.SSHKeys, error)
|
||||
GetSSHConfig(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error)
|
||||
CheckSSHHost(ctx context.Context, principal string, token string) (bool, error)
|
||||
GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]authority.Host, error)
|
||||
GetSSHBastion(ctx context.Context, user string, hostname string) (*authority.Bastion, error)
|
||||
GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]config.Host, error)
|
||||
GetSSHBastion(ctx context.Context, user string, hostname string) (*config.Bastion, error)
|
||||
}
|
||||
|
||||
// SSHSignRequest is the request body of an SSH certificate request.
|
||||
|
@ -86,7 +87,7 @@ type SSHCertificate struct {
|
|||
// SSHGetHostsResponse is the response object that returns the list of valid
|
||||
// hosts for SSH.
|
||||
type SSHGetHostsResponse struct {
|
||||
Hosts []authority.Host `json:"hosts"`
|
||||
Hosts []config.Host `json:"hosts"`
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface. Returns a quoted,
|
||||
|
@ -240,7 +241,7 @@ func (r *SSHBastionRequest) Validate() error {
|
|||
// given host.
|
||||
type SSHBastionResponse struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Bastion *authority.Bastion `json:"bastion,omitempty"`
|
||||
Bastion *config.Bastion `json:"bastion,omitempty"`
|
||||
}
|
||||
|
||||
// SSHSign is an HTTP handler that reads an SignSSHRequest with a one-time-token
|
||||
|
|
36
api/utils.go
36
api/utils.go
|
@ -3,11 +3,14 @@ package api
|
|||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// EnableLogger is an interface that enables response logging for an object.
|
||||
|
@ -64,6 +67,29 @@ func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
|
|||
LogEnabledResponse(w, v)
|
||||
}
|
||||
|
||||
// ProtoJSON writes the passed value into the http.ResponseWriter.
|
||||
func ProtoJSON(w http.ResponseWriter, m proto.Message) {
|
||||
ProtoJSONStatus(w, m, http.StatusOK)
|
||||
}
|
||||
|
||||
// ProtoJSONStatus writes the given value into the http.ResponseWriter and the
|
||||
// given status is written as the status code of the response.
|
||||
func ProtoJSONStatus(w http.ResponseWriter, m proto.Message, status int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
|
||||
b, err := protojson.Marshal(m)
|
||||
if err != nil {
|
||||
LogError(w, err)
|
||||
return
|
||||
}
|
||||
if _, err := w.Write(b); err != nil {
|
||||
LogError(w, err)
|
||||
return
|
||||
}
|
||||
//LogEnabledResponse(w, v)
|
||||
}
|
||||
|
||||
// ReadJSON reads JSON from the request body and stores it in the value
|
||||
// pointed by v.
|
||||
func ReadJSON(r io.Reader, v interface{}) error {
|
||||
|
@ -72,3 +98,13 @@ func ReadJSON(r io.Reader, v interface{}) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadProtoJSON reads JSON from the request body and stores it in the value
|
||||
// pointed by v.
|
||||
func ReadProtoJSON(r io.Reader, m proto.Message) error {
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errs.Wrap(http.StatusBadRequest, err, "error reading request body")
|
||||
}
|
||||
return protojson.Unmarshal(data, m)
|
||||
}
|
||||
|
|
160
authority/admin/api/admin.go
Normal file
160
authority/admin/api/admin.go
Normal file
|
@ -0,0 +1,160 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
// CreateAdminRequest represents the body for a CreateAdmin request.
|
||||
type CreateAdminRequest struct {
|
||||
Subject string `json:"subject"`
|
||||
Provisioner string `json:"provisioner"`
|
||||
Type linkedca.Admin_Type `json:"type"`
|
||||
}
|
||||
|
||||
// Validate validates a new-admin request body.
|
||||
func (car *CreateAdminRequest) Validate() error {
|
||||
if car.Subject == "" {
|
||||
return admin.NewError(admin.ErrorBadRequestType, "subject cannot be empty")
|
||||
}
|
||||
if car.Provisioner == "" {
|
||||
return admin.NewError(admin.ErrorBadRequestType, "provisioner cannot be empty")
|
||||
}
|
||||
switch car.Type {
|
||||
case linkedca.Admin_SUPER_ADMIN, linkedca.Admin_ADMIN:
|
||||
default:
|
||||
return admin.NewError(admin.ErrorBadRequestType, "invalid value for admin type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAdminsResponse for returning a list of admins.
|
||||
type GetAdminsResponse struct {
|
||||
Admins []*linkedca.Admin `json:"admins"`
|
||||
NextCursor string `json:"nextCursor"`
|
||||
}
|
||||
|
||||
// UpdateAdminRequest represents the body for a UpdateAdmin request.
|
||||
type UpdateAdminRequest struct {
|
||||
Type linkedca.Admin_Type `json:"type"`
|
||||
}
|
||||
|
||||
// Validate validates a new-admin request body.
|
||||
func (uar *UpdateAdminRequest) Validate() error {
|
||||
switch uar.Type {
|
||||
case linkedca.Admin_SUPER_ADMIN, linkedca.Admin_ADMIN:
|
||||
default:
|
||||
return admin.NewError(admin.ErrorBadRequestType, "invalid value for admin type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteResponse is the resource for successful DELETE responses.
|
||||
type DeleteResponse struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// GetAdmin returns the requested admin, or an error.
|
||||
func (h *Handler) GetAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
adm, ok := h.auth.LoadAdminByID(id)
|
||||
if !ok {
|
||||
api.WriteError(w, admin.NewError(admin.ErrorNotFoundType,
|
||||
"admin %s not found", id))
|
||||
return
|
||||
}
|
||||
api.ProtoJSON(w, adm)
|
||||
}
|
||||
|
||||
// GetAdmins returns a segment of admins associated with the authority.
|
||||
func (h *Handler) GetAdmins(w http.ResponseWriter, r *http.Request) {
|
||||
cursor, limit, err := api.ParseCursor(r)
|
||||
if err != nil {
|
||||
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err,
|
||||
"error parsing cursor and limit from query params"))
|
||||
return
|
||||
}
|
||||
|
||||
admins, nextCursor, err := h.auth.GetAdmins(cursor, limit)
|
||||
if err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error retrieving paginated admins"))
|
||||
return
|
||||
}
|
||||
api.JSON(w, &GetAdminsResponse{
|
||||
Admins: admins,
|
||||
NextCursor: nextCursor,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateAdmin creates a new admin.
|
||||
func (h *Handler) CreateAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
var body CreateAdminRequest
|
||||
if err := api.ReadJSON(r.Body, &body); err != nil {
|
||||
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := body.Validate(); err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := h.auth.LoadProvisionerByName(body.Provisioner)
|
||||
if err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", body.Provisioner))
|
||||
return
|
||||
}
|
||||
adm := &linkedca.Admin{
|
||||
ProvisionerId: p.GetID(),
|
||||
Subject: body.Subject,
|
||||
Type: body.Type,
|
||||
}
|
||||
// Store to authority collection.
|
||||
if err := h.auth.StoreAdmin(r.Context(), adm, p); err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error storing admin"))
|
||||
return
|
||||
}
|
||||
|
||||
api.ProtoJSONStatus(w, adm, http.StatusCreated)
|
||||
}
|
||||
|
||||
// DeleteAdmin deletes admin.
|
||||
func (h *Handler) DeleteAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
if err := h.auth.RemoveAdmin(r.Context(), id); err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error deleting admin %s", id))
|
||||
return
|
||||
}
|
||||
|
||||
api.JSON(w, &DeleteResponse{Status: "ok"})
|
||||
}
|
||||
|
||||
// UpdateAdmin updates an existing admin.
|
||||
func (h *Handler) UpdateAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
var body UpdateAdminRequest
|
||||
if err := api.ReadJSON(r.Body, &body); err != nil {
|
||||
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err, "error reading request body"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := body.Validate(); err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
adm, err := h.auth.UpdateAdmin(r.Context(), id, &linkedca.Admin{Type: body.Type})
|
||||
if err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error updating admin %s", id))
|
||||
return
|
||||
}
|
||||
|
||||
api.ProtoJSON(w, adm)
|
||||
}
|
41
authority/admin/api/handler.go
Normal file
41
authority/admin/api/handler.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
)
|
||||
|
||||
// Handler is the ACME API request handler.
|
||||
type Handler struct {
|
||||
db admin.DB
|
||||
auth *authority.Authority
|
||||
}
|
||||
|
||||
// NewHandler returns a new Authority Config Handler.
|
||||
func NewHandler(auth *authority.Authority) api.RouterHandler {
|
||||
h := &Handler{db: auth.GetAdminDatabase(), auth: auth}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// Route traffic and implement the Router interface.
|
||||
func (h *Handler) Route(r api.Router) {
|
||||
authnz := func(next nextHTTP) nextHTTP {
|
||||
return h.extractAuthorizeTokenAdmin(h.requireAPIEnabled(next))
|
||||
}
|
||||
|
||||
// Provisioners
|
||||
r.MethodFunc("GET", "/provisioners/{name}", authnz(h.GetProvisioner))
|
||||
r.MethodFunc("GET", "/provisioners", authnz(h.GetProvisioners))
|
||||
r.MethodFunc("POST", "/provisioners", authnz(h.CreateProvisioner))
|
||||
r.MethodFunc("PUT", "/provisioners/{name}", authnz(h.UpdateProvisioner))
|
||||
r.MethodFunc("DELETE", "/provisioners/{name}", authnz(h.DeleteProvisioner))
|
||||
|
||||
// Admins
|
||||
r.MethodFunc("GET", "/admins/{id}", authnz(h.GetAdmin))
|
||||
r.MethodFunc("GET", "/admins", authnz(h.GetAdmins))
|
||||
r.MethodFunc("POST", "/admins", authnz(h.CreateAdmin))
|
||||
r.MethodFunc("PATCH", "/admins/{id}", authnz(h.UpdateAdmin))
|
||||
r.MethodFunc("DELETE", "/admins/{id}", authnz(h.DeleteAdmin))
|
||||
}
|
54
authority/admin/api/middleware.go
Normal file
54
authority/admin/api/middleware.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
)
|
||||
|
||||
type nextHTTP = func(http.ResponseWriter, *http.Request)
|
||||
|
||||
// requireAPIEnabled is a middleware that ensures the Administration API
|
||||
// is enabled before servicing requests.
|
||||
func (h *Handler) requireAPIEnabled(next nextHTTP) nextHTTP {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.auth.IsAdminAPIEnabled() {
|
||||
api.WriteError(w, admin.NewError(admin.ErrorNotImplementedType,
|
||||
"administration API not enabled"))
|
||||
return
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// extractAuthorizeTokenAdmin is a middleware that extracts and caches the bearer token.
|
||||
func (h *Handler) extractAuthorizeTokenAdmin(next nextHTTP) nextHTTP {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tok := r.Header.Get("Authorization")
|
||||
if len(tok) == 0 {
|
||||
api.WriteError(w, admin.NewError(admin.ErrorUnauthorizedType,
|
||||
"missing authorization header token"))
|
||||
return
|
||||
}
|
||||
|
||||
adm, err := h.auth.AuthorizeAdminToken(r, tok)
|
||||
if err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), adminContextKey, adm)
|
||||
next(w, r.WithContext(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
// ContextKey is the key type for storing and searching for ACME request
|
||||
// essentials in the context of a request.
|
||||
type ContextKey string
|
||||
|
||||
const (
|
||||
// adminContextKey account key
|
||||
adminContextKey = ContextKey("admin")
|
||||
)
|
175
authority/admin/api/provisioner.go
Normal file
175
authority/admin/api/provisioner.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
// GetProvisionersResponse is the type for GET /admin/provisioners responses.
|
||||
type GetProvisionersResponse struct {
|
||||
Provisioners provisioner.List `json:"provisioners"`
|
||||
NextCursor string `json:"nextCursor"`
|
||||
}
|
||||
|
||||
// GetProvisioner returns the requested provisioner, or an error.
|
||||
func (h *Handler) GetProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
id := r.URL.Query().Get("id")
|
||||
name := chi.URLParam(r, "name")
|
||||
|
||||
var (
|
||||
p provisioner.Interface
|
||||
err error
|
||||
)
|
||||
if len(id) > 0 {
|
||||
if p, err = h.auth.LoadProvisionerByID(id); err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if p, err = h.auth.LoadProvisionerByName(name); err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
prov, err := h.db.GetProvisioner(ctx, p.GetID())
|
||||
if err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
api.ProtoJSON(w, prov)
|
||||
}
|
||||
|
||||
// GetProvisioners returns the given segment of provisioners associated with the authority.
|
||||
func (h *Handler) GetProvisioners(w http.ResponseWriter, r *http.Request) {
|
||||
cursor, limit, err := api.ParseCursor(r)
|
||||
if err != nil {
|
||||
api.WriteError(w, admin.WrapError(admin.ErrorBadRequestType, err,
|
||||
"error parsing cursor & limit query params"))
|
||||
return
|
||||
}
|
||||
|
||||
p, next, err := h.auth.GetProvisioners(cursor, limit)
|
||||
if err != nil {
|
||||
api.WriteError(w, errs.InternalServerErr(err))
|
||||
return
|
||||
}
|
||||
api.JSON(w, &GetProvisionersResponse{
|
||||
Provisioners: p,
|
||||
NextCursor: next,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateProvisioner creates a new prov.
|
||||
func (h *Handler) CreateProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||
var prov = new(linkedca.Provisioner)
|
||||
if err := api.ReadProtoJSON(r.Body, prov); err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Validate inputs
|
||||
if err := authority.ValidateClaims(prov.Claims); err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.auth.StoreProvisioner(r.Context(), prov); err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error storing provisioner %s", prov.Name))
|
||||
return
|
||||
}
|
||||
api.ProtoJSONStatus(w, prov, http.StatusCreated)
|
||||
}
|
||||
|
||||
// DeleteProvisioner deletes a provisioner.
|
||||
func (h *Handler) DeleteProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.URL.Query().Get("id")
|
||||
name := chi.URLParam(r, "name")
|
||||
|
||||
var (
|
||||
p provisioner.Interface
|
||||
err error
|
||||
)
|
||||
if len(id) > 0 {
|
||||
if p, err = h.auth.LoadProvisionerByID(id); err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", id))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if p, err = h.auth.LoadProvisionerByName(name); err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.auth.RemoveProvisioner(r.Context(), p.GetID()); err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error removing provisioner %s", p.GetName()))
|
||||
return
|
||||
}
|
||||
|
||||
api.JSON(w, &DeleteResponse{Status: "ok"})
|
||||
}
|
||||
|
||||
// UpdateProvisioner updates an existing prov.
|
||||
func (h *Handler) UpdateProvisioner(w http.ResponseWriter, r *http.Request) {
|
||||
var nu = new(linkedca.Provisioner)
|
||||
if err := api.ReadProtoJSON(r.Body, nu); err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
name := chi.URLParam(r, "name")
|
||||
_old, err := h.auth.LoadProvisionerByName(name)
|
||||
if err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner from cached configuration '%s'", name))
|
||||
return
|
||||
}
|
||||
|
||||
old, err := h.db.GetProvisioner(r.Context(), _old.GetID())
|
||||
if err != nil {
|
||||
api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner from db '%s'", _old.GetID()))
|
||||
return
|
||||
}
|
||||
|
||||
if nu.Id != old.Id {
|
||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner ID"))
|
||||
return
|
||||
}
|
||||
if nu.Type != old.Type {
|
||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner type"))
|
||||
return
|
||||
}
|
||||
if nu.AuthorityId != old.AuthorityId {
|
||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner authorityID"))
|
||||
return
|
||||
}
|
||||
if !nu.CreatedAt.AsTime().Equal(old.CreatedAt.AsTime()) {
|
||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner createdAt"))
|
||||
return
|
||||
}
|
||||
if !nu.DeletedAt.AsTime().Equal(old.DeletedAt.AsTime()) {
|
||||
api.WriteError(w, admin.NewErrorISE("cannot change provisioner deletedAt"))
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Validate inputs
|
||||
if err := authority.ValidateClaims(nu.Claims); err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.auth.UpdateProvisioner(r.Context(), nu); err != nil {
|
||||
api.WriteError(w, err)
|
||||
return
|
||||
}
|
||||
api.ProtoJSON(w, nu)
|
||||
}
|
179
authority/admin/db.go
Normal file
179
authority/admin/db.go
Normal file
|
@ -0,0 +1,179 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultAuthorityID is the default AuthorityID. This will be the ID
|
||||
// of the first Authority created, as well as the default AuthorityID
|
||||
// if one is not specified in the configuration.
|
||||
DefaultAuthorityID = "00000000-0000-0000-0000-000000000000"
|
||||
)
|
||||
|
||||
// ErrNotFound is an error that should be used by the authority.DB interface to
|
||||
// indicate that an entity does not exist.
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
// UnmarshalProvisionerDetails unmarshals details type to the specific provisioner details.
|
||||
func UnmarshalProvisionerDetails(typ linkedca.Provisioner_Type, data []byte) (*linkedca.ProvisionerDetails, error) {
|
||||
var v linkedca.ProvisionerDetails
|
||||
switch typ {
|
||||
case linkedca.Provisioner_JWK:
|
||||
v.Data = new(linkedca.ProvisionerDetails_JWK)
|
||||
case linkedca.Provisioner_OIDC:
|
||||
v.Data = new(linkedca.ProvisionerDetails_OIDC)
|
||||
case linkedca.Provisioner_GCP:
|
||||
v.Data = new(linkedca.ProvisionerDetails_GCP)
|
||||
case linkedca.Provisioner_AWS:
|
||||
v.Data = new(linkedca.ProvisionerDetails_AWS)
|
||||
case linkedca.Provisioner_AZURE:
|
||||
v.Data = new(linkedca.ProvisionerDetails_Azure)
|
||||
case linkedca.Provisioner_ACME:
|
||||
v.Data = new(linkedca.ProvisionerDetails_ACME)
|
||||
case linkedca.Provisioner_X5C:
|
||||
v.Data = new(linkedca.ProvisionerDetails_X5C)
|
||||
case linkedca.Provisioner_K8SSA:
|
||||
v.Data = new(linkedca.ProvisionerDetails_K8SSA)
|
||||
case linkedca.Provisioner_SSHPOP:
|
||||
v.Data = new(linkedca.ProvisionerDetails_SSHPOP)
|
||||
case linkedca.Provisioner_SCEP:
|
||||
v.Data = new(linkedca.ProvisionerDetails_SCEP)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported provisioner type %s", typ)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, v.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkedca.ProvisionerDetails{Data: v.Data}, nil
|
||||
}
|
||||
|
||||
// DB is the DB interface expected by the step-ca ACME API.
|
||||
type DB interface {
|
||||
CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error
|
||||
GetProvisioner(ctx context.Context, id string) (*linkedca.Provisioner, error)
|
||||
GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error)
|
||||
UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error
|
||||
DeleteProvisioner(ctx context.Context, id string) error
|
||||
|
||||
CreateAdmin(ctx context.Context, admin *linkedca.Admin) error
|
||||
GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error)
|
||||
GetAdmins(ctx context.Context) ([]*linkedca.Admin, error)
|
||||
UpdateAdmin(ctx context.Context, admin *linkedca.Admin) error
|
||||
DeleteAdmin(ctx context.Context, id string) error
|
||||
}
|
||||
|
||||
// MockDB is an implementation of the DB interface that should only be used as
|
||||
// a mock in tests.
|
||||
type MockDB struct {
|
||||
MockCreateProvisioner func(ctx context.Context, prov *linkedca.Provisioner) error
|
||||
MockGetProvisioner func(ctx context.Context, id string) (*linkedca.Provisioner, error)
|
||||
MockGetProvisioners func(ctx context.Context) ([]*linkedca.Provisioner, error)
|
||||
MockUpdateProvisioner func(ctx context.Context, prov *linkedca.Provisioner) error
|
||||
MockDeleteProvisioner func(ctx context.Context, id string) error
|
||||
|
||||
MockCreateAdmin func(ctx context.Context, adm *linkedca.Admin) error
|
||||
MockGetAdmin func(ctx context.Context, id string) (*linkedca.Admin, error)
|
||||
MockGetAdmins func(ctx context.Context) ([]*linkedca.Admin, error)
|
||||
MockUpdateAdmin func(ctx context.Context, adm *linkedca.Admin) error
|
||||
MockDeleteAdmin func(ctx context.Context, id string) error
|
||||
|
||||
MockError error
|
||||
MockRet1 interface{}
|
||||
}
|
||||
|
||||
// CreateProvisioner mock.
|
||||
func (m *MockDB) CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
|
||||
if m.MockCreateProvisioner != nil {
|
||||
return m.MockCreateProvisioner(ctx, prov)
|
||||
} else if m.MockError != nil {
|
||||
return m.MockError
|
||||
}
|
||||
return m.MockError
|
||||
}
|
||||
|
||||
// GetProvisioner mock.
|
||||
func (m *MockDB) GetProvisioner(ctx context.Context, id string) (*linkedca.Provisioner, error) {
|
||||
if m.MockGetProvisioner != nil {
|
||||
return m.MockGetProvisioner(ctx, id)
|
||||
} else if m.MockError != nil {
|
||||
return nil, m.MockError
|
||||
}
|
||||
return m.MockRet1.(*linkedca.Provisioner), m.MockError
|
||||
}
|
||||
|
||||
// GetProvisioners mock
|
||||
func (m *MockDB) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error) {
|
||||
if m.MockGetProvisioners != nil {
|
||||
return m.MockGetProvisioners(ctx)
|
||||
} else if m.MockError != nil {
|
||||
return nil, m.MockError
|
||||
}
|
||||
return m.MockRet1.([]*linkedca.Provisioner), m.MockError
|
||||
}
|
||||
|
||||
// UpdateProvisioner mock
|
||||
func (m *MockDB) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
|
||||
if m.MockUpdateProvisioner != nil {
|
||||
return m.MockUpdateProvisioner(ctx, prov)
|
||||
}
|
||||
return m.MockError
|
||||
}
|
||||
|
||||
// DeleteProvisioner mock
|
||||
func (m *MockDB) DeleteProvisioner(ctx context.Context, id string) error {
|
||||
if m.MockDeleteProvisioner != nil {
|
||||
return m.MockDeleteProvisioner(ctx, id)
|
||||
}
|
||||
return m.MockError
|
||||
}
|
||||
|
||||
// CreateAdmin mock
|
||||
func (m *MockDB) CreateAdmin(ctx context.Context, admin *linkedca.Admin) error {
|
||||
if m.MockCreateAdmin != nil {
|
||||
return m.MockCreateAdmin(ctx, admin)
|
||||
}
|
||||
return m.MockError
|
||||
}
|
||||
|
||||
// GetAdmin mock.
|
||||
func (m *MockDB) GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) {
|
||||
if m.MockGetAdmin != nil {
|
||||
return m.MockGetAdmin(ctx, id)
|
||||
} else if m.MockError != nil {
|
||||
return nil, m.MockError
|
||||
}
|
||||
return m.MockRet1.(*linkedca.Admin), m.MockError
|
||||
}
|
||||
|
||||
// GetAdmins mock
|
||||
func (m *MockDB) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) {
|
||||
if m.MockGetAdmins != nil {
|
||||
return m.MockGetAdmins(ctx)
|
||||
} else if m.MockError != nil {
|
||||
return nil, m.MockError
|
||||
}
|
||||
return m.MockRet1.([]*linkedca.Admin), m.MockError
|
||||
}
|
||||
|
||||
// UpdateAdmin mock
|
||||
func (m *MockDB) UpdateAdmin(ctx context.Context, adm *linkedca.Admin) error {
|
||||
if m.MockUpdateAdmin != nil {
|
||||
return m.MockUpdateAdmin(ctx, adm)
|
||||
}
|
||||
return m.MockError
|
||||
}
|
||||
|
||||
// DeleteAdmin mock
|
||||
func (m *MockDB) DeleteAdmin(ctx context.Context, id string) error {
|
||||
if m.MockDeleteAdmin != nil {
|
||||
return m.MockDeleteAdmin(ctx, id)
|
||||
}
|
||||
return m.MockError
|
||||
}
|
178
authority/admin/db/nosql/admin.go
Normal file
178
authority/admin/db/nosql/admin.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package nosql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/nosql"
|
||||
"go.step.sm/linkedca"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// dbAdmin is the database representation of the Admin type.
|
||||
type dbAdmin struct {
|
||||
ID string `json:"id"`
|
||||
AuthorityID string `json:"authorityID"`
|
||||
ProvisionerID string `json:"provisionerID"`
|
||||
Subject string `json:"subject"`
|
||||
Type linkedca.Admin_Type `json:"type"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
DeletedAt time.Time `json:"deletedAt"`
|
||||
}
|
||||
|
||||
func (dba *dbAdmin) convert() *linkedca.Admin {
|
||||
return &linkedca.Admin{
|
||||
Id: dba.ID,
|
||||
AuthorityId: dba.AuthorityID,
|
||||
ProvisionerId: dba.ProvisionerID,
|
||||
Subject: dba.Subject,
|
||||
Type: dba.Type,
|
||||
CreatedAt: timestamppb.New(dba.CreatedAt),
|
||||
DeletedAt: timestamppb.New(dba.DeletedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func (dba *dbAdmin) clone() *dbAdmin {
|
||||
u := *dba
|
||||
return &u
|
||||
}
|
||||
|
||||
func (db *DB) getDBAdminBytes(ctx context.Context, id string) ([]byte, error) {
|
||||
data, err := db.db.Get(adminsTable, []byte(id))
|
||||
if nosql.IsErrNotFound(err) {
|
||||
return nil, admin.NewError(admin.ErrorNotFoundType, "admin %s not found", id)
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrapf(err, "error loading admin %s", id)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *DB) unmarshalDBAdmin(data []byte, id string) (*dbAdmin, error) {
|
||||
var dba = new(dbAdmin)
|
||||
if err := json.Unmarshal(data, dba); err != nil {
|
||||
return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", id)
|
||||
}
|
||||
if !dba.DeletedAt.IsZero() {
|
||||
return nil, admin.NewError(admin.ErrorDeletedType, "admin %s is deleted", id)
|
||||
}
|
||||
if dba.AuthorityID != db.authorityID {
|
||||
return nil, admin.NewError(admin.ErrorAuthorityMismatchType,
|
||||
"admin %s is not owned by authority %s", dba.ID, db.authorityID)
|
||||
}
|
||||
return dba, nil
|
||||
}
|
||||
|
||||
func (db *DB) getDBAdmin(ctx context.Context, id string) (*dbAdmin, error) {
|
||||
data, err := db.getDBAdminBytes(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dba, err := db.unmarshalDBAdmin(data, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dba, nil
|
||||
}
|
||||
|
||||
func (db *DB) unmarshalAdmin(data []byte, id string) (*linkedca.Admin, error) {
|
||||
dba, err := db.unmarshalDBAdmin(data, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dba.convert(), nil
|
||||
}
|
||||
|
||||
// GetAdmin retrieves and unmarshals a admin from the database.
|
||||
func (db *DB) GetAdmin(ctx context.Context, id string) (*linkedca.Admin, error) {
|
||||
data, err := db.getDBAdminBytes(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
adm, err := db.unmarshalAdmin(data, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return adm, nil
|
||||
}
|
||||
|
||||
// GetAdmins retrieves and unmarshals all active (not deleted) admins
|
||||
// from the database.
|
||||
// TODO should we be paginating?
|
||||
func (db *DB) GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) {
|
||||
dbEntries, err := db.db.List(adminsTable)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error loading admins")
|
||||
}
|
||||
var admins = []*linkedca.Admin{}
|
||||
for _, entry := range dbEntries {
|
||||
adm, err := db.unmarshalAdmin(entry.Value, string(entry.Key))
|
||||
if err != nil {
|
||||
switch k := err.(type) {
|
||||
case *admin.Error:
|
||||
if k.IsType(admin.ErrorDeletedType) || k.IsType(admin.ErrorAuthorityMismatchType) {
|
||||
continue
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if adm.AuthorityId != db.authorityID {
|
||||
continue
|
||||
}
|
||||
admins = append(admins, adm)
|
||||
}
|
||||
return admins, nil
|
||||
}
|
||||
|
||||
// CreateAdmin stores a new admin to the database.
|
||||
func (db *DB) CreateAdmin(ctx context.Context, adm *linkedca.Admin) error {
|
||||
var err error
|
||||
adm.Id, err = randID()
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error generating random id for admin")
|
||||
}
|
||||
adm.AuthorityId = db.authorityID
|
||||
|
||||
dba := &dbAdmin{
|
||||
ID: adm.Id,
|
||||
AuthorityID: db.authorityID,
|
||||
ProvisionerID: adm.ProvisionerId,
|
||||
Subject: adm.Subject,
|
||||
Type: adm.Type,
|
||||
CreatedAt: clock.Now(),
|
||||
}
|
||||
|
||||
return db.save(ctx, dba.ID, dba, nil, "admin", adminsTable)
|
||||
}
|
||||
|
||||
// UpdateAdmin saves an updated admin to the database.
|
||||
func (db *DB) UpdateAdmin(ctx context.Context, adm *linkedca.Admin) error {
|
||||
old, err := db.getDBAdmin(ctx, adm.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nu := old.clone()
|
||||
nu.Type = adm.Type
|
||||
|
||||
return db.save(ctx, old.ID, nu, old, "admin", adminsTable)
|
||||
}
|
||||
|
||||
// DeleteAdmin saves an updated admin to the database.
|
||||
func (db *DB) DeleteAdmin(ctx context.Context, id string) error {
|
||||
old, err := db.getDBAdmin(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nu := old.clone()
|
||||
nu.DeletedAt = clock.Now()
|
||||
|
||||
return db.save(ctx, old.ID, nu, old, "admin", adminsTable)
|
||||
}
|
1121
authority/admin/db/nosql/admin_test.go
Normal file
1121
authority/admin/db/nosql/admin_test.go
Normal file
File diff suppressed because it is too large
Load diff
88
authority/admin/db/nosql/nosql.go
Normal file
88
authority/admin/db/nosql/nosql.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package nosql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
nosqlDB "github.com/smallstep/nosql/database"
|
||||
"go.step.sm/crypto/randutil"
|
||||
)
|
||||
|
||||
var (
|
||||
adminsTable = []byte("admins")
|
||||
provisionersTable = []byte("provisioners")
|
||||
)
|
||||
|
||||
// DB is a struct that implements the AdminDB interface.
|
||||
type DB struct {
|
||||
db nosqlDB.DB
|
||||
authorityID string
|
||||
}
|
||||
|
||||
// New configures and returns a new Authority DB backend implemented using a nosql DB.
|
||||
func New(db nosqlDB.DB, authorityID string) (*DB, error) {
|
||||
tables := [][]byte{adminsTable, provisionersTable}
|
||||
for _, b := range tables {
|
||||
if err := db.CreateTable(b); err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating table %s",
|
||||
string(b))
|
||||
}
|
||||
}
|
||||
return &DB{db, authorityID}, nil
|
||||
}
|
||||
|
||||
// save writes the new data to the database, overwriting the old data if it
|
||||
// existed.
|
||||
func (db *DB) save(ctx context.Context, id string, nu interface{}, old interface{}, typ string, table []byte) error {
|
||||
var (
|
||||
err error
|
||||
newB []byte
|
||||
)
|
||||
if nu == nil {
|
||||
newB = nil
|
||||
} else {
|
||||
newB, err = json.Marshal(nu)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error marshaling authority type: %s, value: %v", typ, nu)
|
||||
}
|
||||
}
|
||||
var oldB []byte
|
||||
if old == nil {
|
||||
oldB = nil
|
||||
} else {
|
||||
oldB, err = json.Marshal(old)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error marshaling admin type: %s, value: %v", typ, old)
|
||||
}
|
||||
}
|
||||
|
||||
_, swapped, err := db.db.CmpAndSwap(table, []byte(id), oldB, newB)
|
||||
switch {
|
||||
case err != nil:
|
||||
return errors.Wrapf(err, "error saving authority %s", typ)
|
||||
case !swapped:
|
||||
return errors.Errorf("error saving authority %s; changed since last read", typ)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func randID() (val string, err error) {
|
||||
val, err = randutil.UUIDv4()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error generating random alphanumeric ID")
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// Clock that returns time in UTC rounded to seconds.
|
||||
type Clock struct{}
|
||||
|
||||
// Now returns the UTC time rounded to seconds.
|
||||
func (c *Clock) Now() time.Time {
|
||||
return time.Now().UTC().Truncate(time.Second)
|
||||
}
|
||||
|
||||
var clock = new(Clock)
|
211
authority/admin/db/nosql/provisioner.go
Normal file
211
authority/admin/db/nosql/provisioner.go
Normal file
|
@ -0,0 +1,211 @@
|
|||
package nosql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/nosql"
|
||||
"go.step.sm/linkedca"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// dbProvisioner is the database representation of a Provisioner type.
|
||||
type dbProvisioner struct {
|
||||
ID string `json:"id"`
|
||||
AuthorityID string `json:"authorityID"`
|
||||
Type linkedca.Provisioner_Type `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Claims *linkedca.Claims `json:"claims"`
|
||||
Details []byte `json:"details"`
|
||||
X509Template *linkedca.Template `json:"x509Template"`
|
||||
SSHTemplate *linkedca.Template `json:"sshTemplate"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
DeletedAt time.Time `json:"deletedAt"`
|
||||
}
|
||||
|
||||
func (dbp *dbProvisioner) clone() *dbProvisioner {
|
||||
u := *dbp
|
||||
return &u
|
||||
}
|
||||
|
||||
func (dbp *dbProvisioner) convert2linkedca() (*linkedca.Provisioner, error) {
|
||||
details, err := admin.UnmarshalProvisionerDetails(dbp.Type, dbp.Details)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &linkedca.Provisioner{
|
||||
Id: dbp.ID,
|
||||
AuthorityId: dbp.AuthorityID,
|
||||
Type: dbp.Type,
|
||||
Name: dbp.Name,
|
||||
Claims: dbp.Claims,
|
||||
Details: details,
|
||||
X509Template: dbp.X509Template,
|
||||
SshTemplate: dbp.SSHTemplate,
|
||||
CreatedAt: timestamppb.New(dbp.CreatedAt),
|
||||
DeletedAt: timestamppb.New(dbp.DeletedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *DB) getDBProvisionerBytes(ctx context.Context, id string) ([]byte, error) {
|
||||
data, err := db.db.Get(provisionersTable, []byte(id))
|
||||
if nosql.IsErrNotFound(err) {
|
||||
return nil, admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", id)
|
||||
} else if err != nil {
|
||||
return nil, errors.Wrapf(err, "error loading provisioner %s", id)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *DB) unmarshalDBProvisioner(data []byte, id string) (*dbProvisioner, error) {
|
||||
var dbp = new(dbProvisioner)
|
||||
if err := json.Unmarshal(data, dbp); err != nil {
|
||||
return nil, errors.Wrapf(err, "error unmarshaling provisioner %s into dbProvisioner", id)
|
||||
}
|
||||
if !dbp.DeletedAt.IsZero() {
|
||||
return nil, admin.NewError(admin.ErrorDeletedType, "provisioner %s is deleted", id)
|
||||
}
|
||||
if dbp.AuthorityID != db.authorityID {
|
||||
return nil, admin.NewError(admin.ErrorAuthorityMismatchType,
|
||||
"provisioner %s is not owned by authority %s", id, db.authorityID)
|
||||
}
|
||||
return dbp, nil
|
||||
}
|
||||
|
||||
func (db *DB) getDBProvisioner(ctx context.Context, id string) (*dbProvisioner, error) {
|
||||
data, err := db.getDBProvisionerBytes(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbp, err := db.unmarshalDBProvisioner(data, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dbp, nil
|
||||
}
|
||||
|
||||
func (db *DB) unmarshalProvisioner(data []byte, id string) (*linkedca.Provisioner, error) {
|
||||
dbp, err := db.unmarshalDBProvisioner(data, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dbp.convert2linkedca()
|
||||
}
|
||||
|
||||
// GetProvisioner retrieves and unmarshals a provisioner from the database.
|
||||
func (db *DB) GetProvisioner(ctx context.Context, id string) (*linkedca.Provisioner, error) {
|
||||
data, err := db.getDBProvisionerBytes(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prov, err := db.unmarshalProvisioner(data, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return prov, nil
|
||||
}
|
||||
|
||||
// GetProvisioners retrieves and unmarshals all active (not deleted) provisioners
|
||||
// from the database.
|
||||
func (db *DB) GetProvisioners(ctx context.Context) ([]*linkedca.Provisioner, error) {
|
||||
dbEntries, err := db.db.List(provisionersTable)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error loading provisioners")
|
||||
}
|
||||
var provs []*linkedca.Provisioner
|
||||
for _, entry := range dbEntries {
|
||||
prov, err := db.unmarshalProvisioner(entry.Value, string(entry.Key))
|
||||
if err != nil {
|
||||
switch k := err.(type) {
|
||||
case *admin.Error:
|
||||
if k.IsType(admin.ErrorDeletedType) || k.IsType(admin.ErrorAuthorityMismatchType) {
|
||||
continue
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if prov.AuthorityId != db.authorityID {
|
||||
continue
|
||||
}
|
||||
provs = append(provs, prov)
|
||||
}
|
||||
return provs, nil
|
||||
}
|
||||
|
||||
// CreateProvisioner stores a new provisioner to the database.
|
||||
func (db *DB) CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
|
||||
var err error
|
||||
prov.Id, err = randID()
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error generating random id for provisioner")
|
||||
}
|
||||
|
||||
details, err := json.Marshal(prov.Details.GetData())
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error marshaling details when creating provisioner %s", prov.Name)
|
||||
}
|
||||
|
||||
dbp := &dbProvisioner{
|
||||
ID: prov.Id,
|
||||
AuthorityID: db.authorityID,
|
||||
Type: prov.Type,
|
||||
Name: prov.Name,
|
||||
Claims: prov.Claims,
|
||||
Details: details,
|
||||
X509Template: prov.X509Template,
|
||||
SSHTemplate: prov.SshTemplate,
|
||||
CreatedAt: clock.Now(),
|
||||
}
|
||||
|
||||
if err := db.save(ctx, prov.Id, dbp, nil, "provisioner", provisionersTable); err != nil {
|
||||
return admin.WrapErrorISE(err, "error creating provisioner %s", prov.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProvisioner saves an updated provisioner to the database.
|
||||
func (db *DB) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
|
||||
old, err := db.getDBProvisioner(ctx, prov.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nu := old.clone()
|
||||
|
||||
if old.Type != prov.Type {
|
||||
return admin.NewError(admin.ErrorBadRequestType, "cannot update provisioner type")
|
||||
}
|
||||
nu.Name = prov.Name
|
||||
nu.Claims = prov.Claims
|
||||
nu.Details, err = json.Marshal(prov.Details.GetData())
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error marshaling details when updating provisioner %s", prov.Name)
|
||||
}
|
||||
nu.X509Template = prov.X509Template
|
||||
nu.SSHTemplate = prov.SshTemplate
|
||||
|
||||
return db.save(ctx, prov.Id, nu, old, "provisioner", provisionersTable)
|
||||
}
|
||||
|
||||
// DeleteProvisioner saves an updated admin to the database.
|
||||
func (db *DB) DeleteProvisioner(ctx context.Context, id string) error {
|
||||
old, err := db.getDBProvisioner(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nu := old.clone()
|
||||
nu.DeletedAt = clock.Now()
|
||||
|
||||
return db.save(ctx, old.ID, nu, old, "provisioner", provisionersTable)
|
||||
}
|
1221
authority/admin/db/nosql/provisioner_test.go
Normal file
1221
authority/admin/db/nosql/provisioner_test.go
Normal file
File diff suppressed because it is too large
Load diff
223
authority/admin/errors.go
Normal file
223
authority/admin/errors.go
Normal file
|
@ -0,0 +1,223 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
)
|
||||
|
||||
// ProblemType is the type of the Admin problem.
|
||||
type ProblemType int
|
||||
|
||||
const (
|
||||
// ErrorNotFoundType resource not found.
|
||||
ErrorNotFoundType ProblemType = iota
|
||||
// ErrorAuthorityMismatchType resource Authority ID does not match the
|
||||
// context Authority ID.
|
||||
ErrorAuthorityMismatchType
|
||||
// ErrorDeletedType resource has been deleted.
|
||||
ErrorDeletedType
|
||||
// ErrorBadRequestType bad request.
|
||||
ErrorBadRequestType
|
||||
// ErrorNotImplementedType not implemented.
|
||||
ErrorNotImplementedType
|
||||
// ErrorUnauthorizedType internal server error.
|
||||
ErrorUnauthorizedType
|
||||
// ErrorServerInternalType internal server error.
|
||||
ErrorServerInternalType
|
||||
)
|
||||
|
||||
// String returns the string representation of the admin problem type,
|
||||
// fulfilling the Stringer interface.
|
||||
func (ap ProblemType) String() string {
|
||||
switch ap {
|
||||
case ErrorNotFoundType:
|
||||
return "notFound"
|
||||
case ErrorAuthorityMismatchType:
|
||||
return "authorityMismatch"
|
||||
case ErrorDeletedType:
|
||||
return "deleted"
|
||||
case ErrorBadRequestType:
|
||||
return "badRequest"
|
||||
case ErrorNotImplementedType:
|
||||
return "notImplemented"
|
||||
case ErrorUnauthorizedType:
|
||||
return "unauthorized"
|
||||
case ErrorServerInternalType:
|
||||
return "internalServerError"
|
||||
default:
|
||||
return fmt.Sprintf("unsupported error type '%d'", int(ap))
|
||||
}
|
||||
}
|
||||
|
||||
type errorMetadata struct {
|
||||
details string
|
||||
status int
|
||||
typ string
|
||||
String string
|
||||
}
|
||||
|
||||
var (
|
||||
errorServerInternalMetadata = errorMetadata{
|
||||
typ: ErrorServerInternalType.String(),
|
||||
details: "the server experienced an internal error",
|
||||
status: 500,
|
||||
}
|
||||
errorMap = map[ProblemType]errorMetadata{
|
||||
ErrorNotFoundType: {
|
||||
typ: ErrorNotFoundType.String(),
|
||||
details: "resource not found",
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
ErrorAuthorityMismatchType: {
|
||||
typ: ErrorAuthorityMismatchType.String(),
|
||||
details: "resource not owned by authority",
|
||||
status: http.StatusUnauthorized,
|
||||
},
|
||||
ErrorDeletedType: {
|
||||
typ: ErrorDeletedType.String(),
|
||||
details: "resource is deleted",
|
||||
status: http.StatusNotFound,
|
||||
},
|
||||
ErrorNotImplementedType: {
|
||||
typ: ErrorNotImplementedType.String(),
|
||||
details: "not implemented",
|
||||
status: http.StatusNotImplemented,
|
||||
},
|
||||
ErrorBadRequestType: {
|
||||
typ: ErrorBadRequestType.String(),
|
||||
details: "bad request",
|
||||
status: http.StatusBadRequest,
|
||||
},
|
||||
ErrorUnauthorizedType: {
|
||||
typ: ErrorUnauthorizedType.String(),
|
||||
details: "unauthorized",
|
||||
status: http.StatusUnauthorized,
|
||||
},
|
||||
ErrorServerInternalType: errorServerInternalMetadata,
|
||||
}
|
||||
)
|
||||
|
||||
// Error represents an Admin
|
||||
type Error struct {
|
||||
Type string `json:"type"`
|
||||
Detail string `json:"detail"`
|
||||
Message string `json:"message"`
|
||||
Err error `json:"-"`
|
||||
Status int `json:"-"`
|
||||
}
|
||||
|
||||
// IsType returns true if the error type matches the input type.
|
||||
func (e *Error) IsType(pt ProblemType) bool {
|
||||
return pt.String() == e.Type
|
||||
}
|
||||
|
||||
// NewError creates a new Error type.
|
||||
func NewError(pt ProblemType, msg string, args ...interface{}) *Error {
|
||||
return newError(pt, errors.Errorf(msg, args...))
|
||||
}
|
||||
|
||||
func newError(pt ProblemType, err error) *Error {
|
||||
meta, ok := errorMap[pt]
|
||||
if !ok {
|
||||
meta = errorServerInternalMetadata
|
||||
return &Error{
|
||||
Type: meta.typ,
|
||||
Detail: meta.details,
|
||||
Status: meta.status,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return &Error{
|
||||
Type: meta.typ,
|
||||
Detail: meta.details,
|
||||
Status: meta.status,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// NewErrorISE creates a new ErrorServerInternalType Error.
|
||||
func NewErrorISE(msg string, args ...interface{}) *Error {
|
||||
return NewError(ErrorServerInternalType, msg, args...)
|
||||
}
|
||||
|
||||
// WrapError attempts to wrap the internal error.
|
||||
func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Error {
|
||||
switch e := err.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case *Error:
|
||||
if e.Err == nil {
|
||||
e.Err = errors.Errorf(msg+"; "+e.Detail, args...)
|
||||
} else {
|
||||
e.Err = errors.Wrapf(e.Err, msg, args...)
|
||||
}
|
||||
return e
|
||||
default:
|
||||
return newError(typ, errors.Wrapf(err, msg, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// WrapErrorISE shortcut to wrap an internal server error type.
|
||||
func WrapErrorISE(err error, msg string, args ...interface{}) *Error {
|
||||
return WrapError(ErrorServerInternalType, err, msg, args...)
|
||||
}
|
||||
|
||||
// StatusCode returns the status code and implements the StatusCoder interface.
|
||||
func (e *Error) StatusCode() int {
|
||||
return e.Status
|
||||
}
|
||||
|
||||
// Error allows AError to implement the error interface.
|
||||
func (e *Error) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Cause returns the internal error and implements the Causer interface.
|
||||
func (e *Error) Cause() error {
|
||||
if e.Err == nil {
|
||||
return errors.New(e.Detail)
|
||||
}
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// ToLog implements the EnableLogger interface.
|
||||
func (e *Error) ToLog() (interface{}, error) {
|
||||
b, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return nil, WrapErrorISE(err, "error marshaling authority.Error for logging")
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// WriteError writes to w a JSON representation of the given error.
|
||||
func WriteError(w http.ResponseWriter, err *Error) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(err.StatusCode())
|
||||
|
||||
err.Message = err.Err.Error()
|
||||
// Write errors in the response writer
|
||||
if rl, ok := w.(logging.ResponseLogger); ok {
|
||||
rl.WithFields(map[string]interface{}{
|
||||
"error": err.Err,
|
||||
})
|
||||
if os.Getenv("STEPDEBUG") == "1" {
|
||||
if e, ok := err.Err.(errs.StackTracer); ok {
|
||||
rl.WithFields(map[string]interface{}{
|
||||
"stack-trace": fmt.Sprintf("%+v", e),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
243
authority/administrator/collection.go
Normal file
243
authority/administrator/collection.go
Normal file
|
@ -0,0 +1,243 @@
|
|||
package administrator
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
// DefaultAdminLimit is the default limit for listing provisioners.
|
||||
const DefaultAdminLimit = 20
|
||||
|
||||
// DefaultAdminMax is the maximum limit for listing provisioners.
|
||||
const DefaultAdminMax = 100
|
||||
|
||||
type adminSlice []*linkedca.Admin
|
||||
|
||||
func (p adminSlice) Len() int { return len(p) }
|
||||
func (p adminSlice) Less(i, j int) bool { return p[i].Id < p[j].Id }
|
||||
func (p adminSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// Collection is a memory map of admins.
|
||||
type Collection struct {
|
||||
byID *sync.Map
|
||||
bySubProv *sync.Map
|
||||
byProv *sync.Map
|
||||
sorted adminSlice
|
||||
provisioners *provisioner.Collection
|
||||
superCount int
|
||||
superCountByProvisioner map[string]int
|
||||
}
|
||||
|
||||
// NewCollection initializes a collection of provisioners. The given list of
|
||||
// audiences are the audiences used by the JWT provisioner.
|
||||
func NewCollection(provisioners *provisioner.Collection) *Collection {
|
||||
return &Collection{
|
||||
byID: new(sync.Map),
|
||||
byProv: new(sync.Map),
|
||||
bySubProv: new(sync.Map),
|
||||
superCountByProvisioner: map[string]int{},
|
||||
provisioners: provisioners,
|
||||
}
|
||||
}
|
||||
|
||||
// LoadByID a admin by the ID.
|
||||
func (c *Collection) LoadByID(id string) (*linkedca.Admin, bool) {
|
||||
return loadAdmin(c.byID, id)
|
||||
}
|
||||
|
||||
type subProv struct {
|
||||
subject string
|
||||
provisioner string
|
||||
}
|
||||
|
||||
func newSubProv(subject, provisioner string) subProv {
|
||||
return subProv{subject, provisioner}
|
||||
}
|
||||
|
||||
// LoadBySubProv a admin by the subject and provisioner name.
|
||||
func (c *Collection) LoadBySubProv(sub, provName string) (*linkedca.Admin, bool) {
|
||||
return loadAdmin(c.bySubProv, newSubProv(sub, provName))
|
||||
}
|
||||
|
||||
// LoadByProvisioner a admin by the subject and provisioner name.
|
||||
func (c *Collection) LoadByProvisioner(provName string) ([]*linkedca.Admin, bool) {
|
||||
val, ok := c.byProv.Load(provName)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
admins, ok := val.([]*linkedca.Admin)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return admins, true
|
||||
}
|
||||
|
||||
// Store adds an admin to the collection and enforces the uniqueness of
|
||||
// admin IDs and amdin subject <-> provisioner name combos.
|
||||
func (c *Collection) Store(adm *linkedca.Admin, prov provisioner.Interface) error {
|
||||
// Input validation.
|
||||
if adm.ProvisionerId != prov.GetID() {
|
||||
return admin.NewErrorISE("admin.provisionerId does not match provisioner argument")
|
||||
}
|
||||
|
||||
// Store admin always in byID. ID must be unique.
|
||||
if _, loaded := c.byID.LoadOrStore(adm.Id, adm); loaded {
|
||||
return errors.New("cannot add multiple admins with the same id")
|
||||
}
|
||||
|
||||
provName := prov.GetName()
|
||||
// Store admin always in bySubProv. Subject <-> ProvisionerName must be unique.
|
||||
if _, loaded := c.bySubProv.LoadOrStore(newSubProv(adm.Subject, provName), adm); loaded {
|
||||
c.byID.Delete(adm.Id)
|
||||
return errors.New("cannot add multiple admins with the same subject and provisioner")
|
||||
}
|
||||
|
||||
var isSuper = (adm.Type == linkedca.Admin_SUPER_ADMIN)
|
||||
if admins, ok := c.LoadByProvisioner(provName); ok {
|
||||
c.byProv.Store(provName, append(admins, adm))
|
||||
if isSuper {
|
||||
c.superCountByProvisioner[provName]++
|
||||
}
|
||||
} else {
|
||||
c.byProv.Store(provName, []*linkedca.Admin{adm})
|
||||
if isSuper {
|
||||
c.superCountByProvisioner[provName] = 1
|
||||
}
|
||||
}
|
||||
if isSuper {
|
||||
c.superCount++
|
||||
}
|
||||
|
||||
c.sorted = append(c.sorted, adm)
|
||||
sort.Sort(c.sorted)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove deletes an admin from all associated collections and lists.
|
||||
func (c *Collection) Remove(id string) error {
|
||||
adm, ok := c.LoadByID(id)
|
||||
if !ok {
|
||||
return admin.NewError(admin.ErrorNotFoundType, "admin %s not found", id)
|
||||
}
|
||||
if adm.Type == linkedca.Admin_SUPER_ADMIN && c.SuperCount() == 1 {
|
||||
return admin.NewError(admin.ErrorBadRequestType, "cannot remove the last super admin")
|
||||
}
|
||||
prov, ok := c.provisioners.Load(adm.ProvisionerId)
|
||||
if !ok {
|
||||
return admin.NewError(admin.ErrorNotFoundType,
|
||||
"provisioner %s for admin %s not found", adm.ProvisionerId, id)
|
||||
}
|
||||
provName := prov.GetName()
|
||||
adminsByProv, ok := c.LoadByProvisioner(provName)
|
||||
if !ok {
|
||||
return admin.NewError(admin.ErrorNotFoundType,
|
||||
"admins not found for provisioner %s", provName)
|
||||
}
|
||||
|
||||
// Find index in sorted list.
|
||||
sortedIndex := sort.Search(c.sorted.Len(), func(i int) bool { return c.sorted[i].Id >= adm.Id })
|
||||
if c.sorted[sortedIndex].Id != adm.Id {
|
||||
return admin.NewError(admin.ErrorNotFoundType,
|
||||
"admin %s not found in sorted list", adm.Id)
|
||||
}
|
||||
|
||||
var found bool
|
||||
for i, a := range adminsByProv {
|
||||
if a.Id == adm.Id {
|
||||
// Remove admin from list. https://stackoverflow.com/questions/37334119/how-to-delete-an-element-from-a-slice-in-golang
|
||||
// Order does not matter.
|
||||
adminsByProv[i] = adminsByProv[len(adminsByProv)-1]
|
||||
c.byProv.Store(provName, adminsByProv[:len(adminsByProv)-1])
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return admin.NewError(admin.ErrorNotFoundType,
|
||||
"admin %s not found in adminsByProvisioner list", adm.Id)
|
||||
}
|
||||
|
||||
// Remove index in sorted list
|
||||
copy(c.sorted[sortedIndex:], c.sorted[sortedIndex+1:]) // Shift a[i+1:] left one index.
|
||||
c.sorted[len(c.sorted)-1] = nil // Erase last element (write zero value).
|
||||
c.sorted = c.sorted[:len(c.sorted)-1] // Truncate slice.
|
||||
|
||||
c.byID.Delete(adm.Id)
|
||||
c.bySubProv.Delete(newSubProv(adm.Subject, provName))
|
||||
|
||||
if adm.Type == linkedca.Admin_SUPER_ADMIN {
|
||||
c.superCount--
|
||||
c.superCountByProvisioner[provName]--
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates the given admin in all related lists and collections.
|
||||
func (c *Collection) Update(id string, nu *linkedca.Admin) (*linkedca.Admin, error) {
|
||||
adm, ok := c.LoadByID(id)
|
||||
if !ok {
|
||||
return nil, admin.NewError(admin.ErrorNotFoundType, "admin %s not found", adm.Id)
|
||||
}
|
||||
if adm.Type == nu.Type {
|
||||
return adm, nil
|
||||
}
|
||||
if adm.Type == linkedca.Admin_SUPER_ADMIN && c.SuperCount() == 1 {
|
||||
return nil, admin.NewError(admin.ErrorBadRequestType, "cannot change role of last super admin")
|
||||
}
|
||||
|
||||
adm.Type = nu.Type
|
||||
return adm, nil
|
||||
}
|
||||
|
||||
// SuperCount returns the total number of admins.
|
||||
func (c *Collection) SuperCount() int {
|
||||
return c.superCount
|
||||
}
|
||||
|
||||
// SuperCountByProvisioner returns the total number of admins.
|
||||
func (c *Collection) SuperCountByProvisioner(provName string) int {
|
||||
if cnt, ok := c.superCountByProvisioner[provName]; ok {
|
||||
return cnt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Find implements pagination on a list of sorted admins.
|
||||
func (c *Collection) Find(cursor string, limit int) ([]*linkedca.Admin, string) {
|
||||
switch {
|
||||
case limit <= 0:
|
||||
limit = DefaultAdminLimit
|
||||
case limit > DefaultAdminMax:
|
||||
limit = DefaultAdminMax
|
||||
}
|
||||
|
||||
n := c.sorted.Len()
|
||||
i := sort.Search(n, func(i int) bool { return c.sorted[i].Id >= cursor })
|
||||
|
||||
slice := []*linkedca.Admin{}
|
||||
for ; i < n && len(slice) < limit; i++ {
|
||||
slice = append(slice, c.sorted[i])
|
||||
}
|
||||
|
||||
if i < n {
|
||||
return slice, c.sorted[i].Id
|
||||
}
|
||||
return slice, ""
|
||||
}
|
||||
|
||||
func loadAdmin(m *sync.Map, key interface{}) (*linkedca.Admin, bool) {
|
||||
val, ok := m.Load(key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
adm, ok := val.(*linkedca.Admin)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return adm, true
|
||||
}
|
97
authority/admins.go
Normal file
97
authority/admins.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package authority
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
// LoadAdminByID returns an *linkedca.Admin with the given ID.
|
||||
func (a *Authority) LoadAdminByID(id string) (*linkedca.Admin, bool) {
|
||||
a.adminMutex.RLock()
|
||||
defer a.adminMutex.RUnlock()
|
||||
return a.admins.LoadByID(id)
|
||||
}
|
||||
|
||||
// LoadAdminBySubProv returns an *linkedca.Admin with the given ID.
|
||||
func (a *Authority) LoadAdminBySubProv(subject, provisioner string) (*linkedca.Admin, bool) {
|
||||
a.adminMutex.RLock()
|
||||
defer a.adminMutex.RUnlock()
|
||||
return a.admins.LoadBySubProv(subject, provisioner)
|
||||
}
|
||||
|
||||
// GetAdmins returns a map listing each provisioner and the JWK Key Set
|
||||
// with their public keys.
|
||||
func (a *Authority) GetAdmins(cursor string, limit int) ([]*linkedca.Admin, string, error) {
|
||||
a.adminMutex.RLock()
|
||||
defer a.adminMutex.RUnlock()
|
||||
admins, nextCursor := a.admins.Find(cursor, limit)
|
||||
return admins, nextCursor, nil
|
||||
}
|
||||
|
||||
// StoreAdmin stores an *linkedca.Admin to the authority.
|
||||
func (a *Authority) StoreAdmin(ctx context.Context, adm *linkedca.Admin, prov provisioner.Interface) error {
|
||||
a.adminMutex.Lock()
|
||||
defer a.adminMutex.Unlock()
|
||||
|
||||
if adm.ProvisionerId != prov.GetID() {
|
||||
return admin.NewErrorISE("admin.provisionerId does not match provisioner argument")
|
||||
}
|
||||
|
||||
if _, ok := a.admins.LoadBySubProv(adm.Subject, prov.GetName()); ok {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"admin with subject %s and provisioner %s already exists", adm.Subject, prov.GetName())
|
||||
}
|
||||
// Store to database -- this will set the ID.
|
||||
if err := a.adminDB.CreateAdmin(ctx, adm); err != nil {
|
||||
return admin.WrapErrorISE(err, "error creating admin")
|
||||
}
|
||||
if err := a.admins.Store(adm, prov); err != nil {
|
||||
if err := a.reloadAdminResources(ctx); err != nil {
|
||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed admin store")
|
||||
}
|
||||
return admin.WrapErrorISE(err, "error storing admin in authority cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateAdmin stores an *linkedca.Admin to the authority.
|
||||
func (a *Authority) UpdateAdmin(ctx context.Context, id string, nu *linkedca.Admin) (*linkedca.Admin, error) {
|
||||
a.adminMutex.Lock()
|
||||
defer a.adminMutex.Unlock()
|
||||
adm, err := a.admins.Update(id, nu)
|
||||
if err != nil {
|
||||
return nil, admin.WrapErrorISE(err, "error updating cached admin %s", id)
|
||||
}
|
||||
if err := a.adminDB.UpdateAdmin(ctx, adm); err != nil {
|
||||
if err := a.reloadAdminResources(ctx); err != nil {
|
||||
return nil, admin.WrapErrorISE(err, "error reloading admin resources on failed admin update")
|
||||
}
|
||||
return nil, admin.WrapErrorISE(err, "error updating admin %s", id)
|
||||
}
|
||||
return adm, nil
|
||||
}
|
||||
|
||||
// RemoveAdmin removes an *linkedca.Admin from the authority.
|
||||
func (a *Authority) RemoveAdmin(ctx context.Context, id string) error {
|
||||
a.adminMutex.Lock()
|
||||
defer a.adminMutex.Unlock()
|
||||
|
||||
return a.removeAdmin(ctx, id)
|
||||
}
|
||||
|
||||
// removeAdmin helper that assumes lock.
|
||||
func (a *Authority) removeAdmin(ctx context.Context, id string) error {
|
||||
if err := a.admins.Remove(id); err != nil {
|
||||
return admin.WrapErrorISE(err, "error removing admin %s from authority cache", id)
|
||||
}
|
||||
if err := a.adminDB.DeleteAdmin(ctx, id); err != nil {
|
||||
if err := a.reloadAdminResources(ctx); err != nil {
|
||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed admin remove")
|
||||
}
|
||||
return admin.WrapErrorISE(err, "error deleting admin %s", id)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -12,8 +12,13 @@ import (
|
|||
|
||||
"github.com/smallstep/certificates/cas"
|
||||
"github.com/smallstep/certificates/scep"
|
||||
"go.step.sm/linkedca"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
adminDBNosql "github.com/smallstep/certificates/authority/admin/db/nosql"
|
||||
"github.com/smallstep/certificates/authority/administrator"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
casapi "github.com/smallstep/certificates/cas/apiv1"
|
||||
"github.com/smallstep/certificates/db"
|
||||
|
@ -21,25 +26,25 @@ import (
|
|||
kmsapi "github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/kms/sshagentkms"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"github.com/smallstep/nosql"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const (
|
||||
legacyAuthority = "step-certificate-authority"
|
||||
)
|
||||
|
||||
// Authority implements the Certificate Authority internal interface.
|
||||
type Authority struct {
|
||||
config *Config
|
||||
config *config.Config
|
||||
keyManager kms.KeyManager
|
||||
provisioners *provisioner.Collection
|
||||
admins *administrator.Collection
|
||||
db db.AuthDB
|
||||
adminDB admin.DB
|
||||
templates *templates.Templates
|
||||
|
||||
// X509 CA
|
||||
x509CAService cas.CertificateAuthorityService
|
||||
rootX509Certs []*x509.Certificate
|
||||
rootX509CertPool *x509.CertPool
|
||||
federatedX509Certs []*x509.Certificate
|
||||
certificates *sync.Map
|
||||
|
||||
|
@ -59,14 +64,16 @@ type Authority struct {
|
|||
startTime time.Time
|
||||
|
||||
// Custom functions
|
||||
sshBastionFunc func(ctx context.Context, user, hostname string) (*Bastion, error)
|
||||
sshBastionFunc func(ctx context.Context, user, hostname string) (*config.Bastion, error)
|
||||
sshCheckHostFunc func(ctx context.Context, principal string, tok string, roots []*x509.Certificate) (bool, error)
|
||||
sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]Host, error)
|
||||
sshGetHostsFunc func(ctx context.Context, cert *x509.Certificate) ([]config.Host, error)
|
||||
getIdentityFunc provisioner.GetIdentityFunc
|
||||
|
||||
adminMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates and initiates a new Authority type.
|
||||
func New(config *Config, opts ...Option) (*Authority, error) {
|
||||
func New(config *config.Config, opts ...Option) (*Authority, error) {
|
||||
err := config.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -96,7 +103,7 @@ func New(config *Config, opts ...Option) (*Authority, error) {
|
|||
// project without the limitations of the config.
|
||||
func NewEmbedded(opts ...Option) (*Authority, error) {
|
||||
a := &Authority{
|
||||
config: &Config{},
|
||||
config: &config.Config{},
|
||||
certificates: new(sync.Map),
|
||||
}
|
||||
|
||||
|
@ -120,7 +127,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
|
|||
}
|
||||
|
||||
// Initialize config required fields.
|
||||
a.config.init()
|
||||
a.config.Init()
|
||||
|
||||
// Initialize authority from options or configuration.
|
||||
if err := a.init(); err != nil {
|
||||
|
@ -130,6 +137,65 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
|
|||
return a, nil
|
||||
}
|
||||
|
||||
// reloadAdminResources reloads admins and provisioners from the DB.
|
||||
func (a *Authority) reloadAdminResources(ctx context.Context) error {
|
||||
var (
|
||||
provList provisioner.List
|
||||
adminList []*linkedca.Admin
|
||||
)
|
||||
if a.config.AuthorityConfig.EnableAdmin {
|
||||
provs, err := a.adminDB.GetProvisioners(ctx)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error getting provisioners to initialize authority")
|
||||
}
|
||||
provList, err = provisionerListToCertificates(provs)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error converting provisioner list to certificates")
|
||||
}
|
||||
adminList, err = a.adminDB.GetAdmins(ctx)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error getting admins to initialize authority")
|
||||
}
|
||||
} else {
|
||||
provList = a.config.AuthorityConfig.Provisioners
|
||||
adminList = a.config.AuthorityConfig.Admins
|
||||
}
|
||||
|
||||
provisionerConfig, err := a.generateProvisionerConfig(ctx)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error generating provisioner config")
|
||||
}
|
||||
|
||||
// Create provisioner collection.
|
||||
provClxn := provisioner.NewCollection(provisionerConfig.Audiences)
|
||||
for _, p := range provList {
|
||||
if err := p.Init(*provisionerConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := provClxn.Store(p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Create admin collection.
|
||||
adminClxn := administrator.NewCollection(provClxn)
|
||||
for _, adm := range adminList {
|
||||
p, ok := provClxn.Load(adm.ProvisionerId)
|
||||
if !ok {
|
||||
return admin.NewErrorISE("provisioner %s not found when loading admin %s",
|
||||
adm.ProvisionerId, adm.Id)
|
||||
}
|
||||
if err := adminClxn.Store(adm, p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
a.config.AuthorityConfig.Provisioners = provList
|
||||
a.provisioners = provClxn
|
||||
a.config.AuthorityConfig.Admins = adminList
|
||||
a.admins = adminClxn
|
||||
return nil
|
||||
}
|
||||
|
||||
// init performs validation and initializes the fields of an Authority struct.
|
||||
func (a *Authority) init() error {
|
||||
// Check if handler has already been validated/initialized.
|
||||
|
@ -216,6 +282,11 @@ func (a *Authority) init() error {
|
|||
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
|
||||
}
|
||||
|
||||
a.rootX509CertPool = x509.NewCertPool()
|
||||
for _, cert := range a.rootX509Certs {
|
||||
a.rootX509CertPool.AddCert(cert)
|
||||
}
|
||||
|
||||
// Read federated certificates and store them in the certificates map.
|
||||
if len(a.federatedX509Certs) == 0 {
|
||||
a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots))
|
||||
|
@ -317,30 +388,11 @@ func (a *Authority) init() error {
|
|||
tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, a.sshCAUserFederatedCerts[1:]...)
|
||||
}
|
||||
|
||||
// Merge global and configuration claims
|
||||
claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, globalProvisionerClaims)
|
||||
if err != nil {
|
||||
return err
|
||||
// Check if a KMS with decryption capability is required and available
|
||||
if a.requiresDecrypter() {
|
||||
if _, ok := a.keyManager.(kmsapi.Decrypter); !ok {
|
||||
return errors.New("keymanager doesn't provide crypto.Decrypter")
|
||||
}
|
||||
// TODO: should we also be combining the ssh federated roots here?
|
||||
// If we rotate ssh roots keys, sshpop provisioner will lose ability to
|
||||
// validate old SSH certificates, unless they are added as federated certs.
|
||||
sshKeys, err := a.GetSSHRoots(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Initialize provisioners
|
||||
audiences := a.config.getAudiences()
|
||||
a.provisioners = provisioner.NewCollection(audiences)
|
||||
config := provisioner.Config{
|
||||
Claims: claimer.Claims(),
|
||||
Audiences: audiences,
|
||||
DB: a.db,
|
||||
SSHKeys: &provisioner.SSHKeys{
|
||||
UserKeys: sshKeys.UserKeys,
|
||||
HostKeys: sshKeys.HostKeys,
|
||||
},
|
||||
GetIdentityFunc: a.getIdentityFunc,
|
||||
}
|
||||
|
||||
// Check if a KMS with decryption capability is required and available
|
||||
|
@ -386,14 +438,42 @@ func (a *Authority) init() error {
|
|||
// TODO: mimick the x509CAService GetCertificateAuthority here too?
|
||||
}
|
||||
|
||||
// Store all the provisioners
|
||||
for _, p := range a.config.AuthorityConfig.Provisioners {
|
||||
if err := p.Init(config); err != nil {
|
||||
if a.config.AuthorityConfig.EnableAdmin {
|
||||
// Initialize step-ca Admin Database if it's not already initialized using
|
||||
// WithAdminDB.
|
||||
if a.adminDB == nil {
|
||||
// Check if AuthConfig already exists
|
||||
a.adminDB, err = adminDBNosql.New(a.db.(nosql.DB), admin.DefaultAuthorityID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.provisioners.Store(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
provs, err := a.adminDB.GetProvisioners(context.Background())
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error loading provisioners to initialize authority")
|
||||
}
|
||||
if len(provs) == 0 {
|
||||
// Create First Provisioner
|
||||
prov, err := CreateFirstProvisioner(context.Background(), a.adminDB, a.config.Password)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error creating first provisioner")
|
||||
}
|
||||
|
||||
// Create first admin
|
||||
if err := a.adminDB.CreateAdmin(context.Background(), &linkedca.Admin{
|
||||
ProvisionerId: prov.Id,
|
||||
Subject: "step",
|
||||
Type: linkedca.Admin_SUPER_ADMIN,
|
||||
}); err != nil {
|
||||
return admin.WrapErrorISE(err, "error creating first admin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load Provisioners and Admins
|
||||
if err := a.reloadAdminResources(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Configure templates, currently only ssh templates are supported.
|
||||
|
@ -423,6 +503,17 @@ func (a *Authority) GetDatabase() db.AuthDB {
|
|||
return a.db
|
||||
}
|
||||
|
||||
// GetAdminDatabase returns the admin database, if one exists.
|
||||
func (a *Authority) GetAdminDatabase() admin.DB {
|
||||
return a.adminDB
|
||||
}
|
||||
|
||||
// IsAdminAPIEnabled returns a boolean indicating whether the Admin API has
|
||||
// been enabled.
|
||||
func (a *Authority) IsAdminAPIEnabled() bool {
|
||||
return a.config.AuthorityConfig.EnableAdmin
|
||||
}
|
||||
|
||||
// Shutdown safely shuts down any clients, databases, etc. held by the Authority.
|
||||
func (a *Authority) Shutdown() error {
|
||||
if err := a.keyManager.Close(); err != nil {
|
||||
|
|
|
@ -454,8 +454,6 @@ func TestAuthority_GetSCEPService(t *testing.T) {
|
|||
// getIdentityFunc: tt.fields.getIdentityFunc,
|
||||
// }
|
||||
a, err := New(tt.fields.config)
|
||||
fmt.Println(err)
|
||||
fmt.Println(a)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Authority.New(), error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
|
|
@ -7,10 +7,13 @@ import (
|
|||
"encoding/hex"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/linkedca"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -73,23 +76,119 @@ func (a *Authority) authorizeToken(ctx context.Context, token string) (provision
|
|||
// Store the token to protect against reuse unless it's skipped.
|
||||
// If we cannot get a token id from the provisioner, just hash the token.
|
||||
if !SkipTokenReuseFromContext(ctx) {
|
||||
if reuseKey, err := p.GetTokenID(token); err == nil {
|
||||
if err = a.UseToken(token, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// AuthorizeAdminToken authorize an Admin token.
|
||||
func (a *Authority) AuthorizeAdminToken(r *http.Request, token string) (*linkedca.Admin, error) {
|
||||
jwt, err := jose.ParseSigned(token)
|
||||
if err != nil {
|
||||
return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "adminHandler.authorizeToken; error parsing x5c token")
|
||||
}
|
||||
|
||||
verifiedChains, err := jwt.Headers[0].Certificates(x509.VerifyOptions{
|
||||
Roots: a.rootX509CertPool,
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, admin.WrapError(admin.ErrorUnauthorizedType, err,
|
||||
"adminHandler.authorizeToken; error verifying x5c certificate chain in token")
|
||||
}
|
||||
leaf := verifiedChains[0][0]
|
||||
|
||||
if leaf.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
|
||||
return nil, admin.NewError(admin.ErrorUnauthorizedType, "adminHandler.authorizeToken; certificate used to sign x5c token cannot be used for digital signature")
|
||||
}
|
||||
|
||||
// Using the leaf certificates key to validate the claims accomplishes two
|
||||
// things:
|
||||
// 1. Asserts that the private key used to sign the token corresponds
|
||||
// to the public certificate in the `x5c` header of the token.
|
||||
// 2. Asserts that the claims are valid - have not been tampered with.
|
||||
var claims jose.Claims
|
||||
if err = jwt.Claims(leaf.PublicKey, &claims); err != nil {
|
||||
return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "adminHandler.authorizeToken; error parsing x5c claims")
|
||||
}
|
||||
|
||||
prov, err := a.LoadProvisionerByCertificate(leaf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check that the token has not been used.
|
||||
if err = a.UseToken(token, prov); err != nil {
|
||||
return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "adminHandler.authorizeToken; error with reuse token")
|
||||
}
|
||||
|
||||
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
||||
// more than a few minutes.
|
||||
if err = claims.ValidateWithLeeway(jose.Expected{
|
||||
Issuer: prov.GetName(),
|
||||
Time: time.Now().UTC(),
|
||||
}, time.Minute); err != nil {
|
||||
return nil, admin.WrapError(admin.ErrorUnauthorizedType, err, "x5c.authorizeToken; invalid x5c claims")
|
||||
}
|
||||
|
||||
// validate audience: path matches the current path
|
||||
if r.URL.Path != claims.Audience[0] {
|
||||
return nil, admin.NewError(admin.ErrorUnauthorizedType,
|
||||
"x5c.authorizeToken; x5c token has invalid audience "+
|
||||
"claim (aud); expected %s, but got %s", r.URL.Path, claims.Audience)
|
||||
}
|
||||
|
||||
if claims.Subject == "" {
|
||||
return nil, admin.NewError(admin.ErrorUnauthorizedType,
|
||||
"x5c.authorizeToken; x5c token subject cannot be empty")
|
||||
}
|
||||
|
||||
var (
|
||||
ok bool
|
||||
adm *linkedca.Admin
|
||||
)
|
||||
adminFound := false
|
||||
adminSANs := append([]string{leaf.Subject.CommonName}, leaf.DNSNames...)
|
||||
adminSANs = append(adminSANs, leaf.EmailAddresses...)
|
||||
for _, san := range adminSANs {
|
||||
if adm, ok = a.LoadAdminBySubProv(san, claims.Issuer); ok {
|
||||
adminFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !adminFound {
|
||||
return nil, admin.NewError(admin.ErrorUnauthorizedType,
|
||||
"adminHandler.authorizeToken; unable to load admin with subject(s) %s and provisioner '%s'",
|
||||
adminSANs, claims.Issuer)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/admin/admins") && (r.Method != "GET") && adm.Type != linkedca.Admin_SUPER_ADMIN {
|
||||
return nil, admin.NewError(admin.ErrorUnauthorizedType, "must have super admin access to make this request")
|
||||
}
|
||||
|
||||
return adm, nil
|
||||
}
|
||||
|
||||
// UseToken stores the token to protect against reuse.
|
||||
func (a *Authority) UseToken(token string, prov provisioner.Interface) error {
|
||||
if reuseKey, err := prov.GetTokenID(token); err == nil {
|
||||
if reuseKey == "" {
|
||||
sum := sha256.Sum256([]byte(token))
|
||||
reuseKey = strings.ToLower(hex.EncodeToString(sum[:]))
|
||||
}
|
||||
ok, err := a.db.UseToken(reuseKey, token)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err,
|
||||
return errs.Wrap(http.StatusInternalServerError, err,
|
||||
"authority.authorizeToken: failed when attempting to store token")
|
||||
}
|
||||
if !ok {
|
||||
return nil, errs.Unauthorized("authority.authorizeToken: token already used")
|
||||
return errs.Unauthorized("authority.authorizeToken: token already used")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authorize grabs the method from the context and authorizes the request by
|
||||
|
|
|
@ -822,7 +822,7 @@ func TestAuthority_authorizeRenew(t *testing.T) {
|
|||
return &authorizeTest{
|
||||
auth: a,
|
||||
cert: renewDisabledCrt,
|
||||
err: errors.New("authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner renew_disabled:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"),
|
||||
err: errors.New("authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner 'renew_disabled'"),
|
||||
code: http.StatusUnauthorized,
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,298 +1,46 @@
|
|||
package authority
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
import "github.com/smallstep/certificates/authority/config"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
cas "github.com/smallstep/certificates/cas/apiv1"
|
||||
"github.com/smallstep/certificates/db"
|
||||
kms "github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
)
|
||||
// Config is an alias to support older APIs.
|
||||
type Config = config.Config
|
||||
|
||||
var (
|
||||
// DefaultTLSOptions represents the default TLS version as well as the cipher
|
||||
// suites used in the TLS certificates.
|
||||
DefaultTLSOptions = TLSOptions{
|
||||
CipherSuites: CipherSuites{
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
},
|
||||
MinVersion: 1.2,
|
||||
MaxVersion: 1.2,
|
||||
Renegotiation: false,
|
||||
}
|
||||
defaultBackdate = time.Minute
|
||||
defaultDisableRenewal = false
|
||||
defaultEnableSSHCA = false
|
||||
globalProvisionerClaims = provisioner.Claims{
|
||||
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, // TLS certs
|
||||
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
||||
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
||||
DisableRenewal: &defaultDisableRenewal,
|
||||
MinUserSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // User SSH certs
|
||||
MaxUserSSHDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
||||
DefaultUserSSHDur: &provisioner.Duration{Duration: 16 * time.Hour},
|
||||
MinHostSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // Host SSH certs
|
||||
MaxHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour},
|
||||
DefaultHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour},
|
||||
EnableSSHCA: &defaultEnableSSHCA,
|
||||
}
|
||||
)
|
||||
// LoadConfiguration is an alias to support older APIs.
|
||||
var LoadConfiguration = config.LoadConfiguration
|
||||
|
||||
// Config represents the CA configuration and it's mapped to a JSON object.
|
||||
type Config struct {
|
||||
Root multiString `json:"root"`
|
||||
FederatedRoots []string `json:"federatedRoots"`
|
||||
IntermediateCert string `json:"crt"`
|
||||
IntermediateKey string `json:"key"`
|
||||
Address string `json:"address"`
|
||||
InsecureAddress string `json:"insecureAddress"`
|
||||
DNSNames []string `json:"dnsNames"`
|
||||
KMS *kms.Options `json:"kms,omitempty"`
|
||||
SSH *SSHConfig `json:"ssh,omitempty"`
|
||||
Logger json.RawMessage `json:"logger,omitempty"`
|
||||
DB *db.Config `json:"db,omitempty"`
|
||||
Monitoring json.RawMessage `json:"monitoring,omitempty"`
|
||||
AuthorityConfig *AuthConfig `json:"authority,omitempty"`
|
||||
TLS *TLSOptions `json:"tls,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Templates *templates.Templates `json:"templates,omitempty"`
|
||||
}
|
||||
// AuthConfig is an alias to support older APIs.
|
||||
type AuthConfig = config.AuthConfig
|
||||
|
||||
// ASN1DN contains ASN1.DN attributes that are used in Subject and Issuer
|
||||
// x509 Certificate blocks.
|
||||
type ASN1DN struct {
|
||||
Country string `json:"country,omitempty" step:"country"`
|
||||
Organization string `json:"organization,omitempty" step:"organization"`
|
||||
OrganizationalUnit string `json:"organizationalUnit,omitempty" step:"organizationalUnit"`
|
||||
Locality string `json:"locality,omitempty" step:"locality"`
|
||||
Province string `json:"province,omitempty" step:"province"`
|
||||
StreetAddress string `json:"streetAddress,omitempty" step:"streetAddress"`
|
||||
CommonName string `json:"commonName,omitempty" step:"commonName"`
|
||||
}
|
||||
// TLS
|
||||
|
||||
// AuthConfig represents the configuration options for the authority. An
|
||||
// underlaying registration authority can also be configured using the
|
||||
// cas.Options.
|
||||
type AuthConfig struct {
|
||||
*cas.Options
|
||||
Provisioners provisioner.List `json:"provisioners"`
|
||||
Template *ASN1DN `json:"template,omitempty"`
|
||||
Claims *provisioner.Claims `json:"claims,omitempty"`
|
||||
DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"`
|
||||
Backdate *provisioner.Duration `json:"backdate,omitempty"`
|
||||
}
|
||||
// ASN1DN is an alias to support older APIs.
|
||||
type ASN1DN = config.ASN1DN
|
||||
|
||||
// init initializes the required fields in the AuthConfig if they are not
|
||||
// provided.
|
||||
func (c *AuthConfig) init() {
|
||||
if c.Provisioners == nil {
|
||||
c.Provisioners = provisioner.List{}
|
||||
}
|
||||
if c.Template == nil {
|
||||
c.Template = &ASN1DN{}
|
||||
}
|
||||
if c.Backdate == nil {
|
||||
c.Backdate = &provisioner.Duration{
|
||||
Duration: defaultBackdate,
|
||||
}
|
||||
}
|
||||
}
|
||||
// DefaultTLSOptions is an alias to support older APIs.
|
||||
var DefaultTLSOptions = config.DefaultTLSOptions
|
||||
|
||||
// Validate validates the authority configuration.
|
||||
func (c *AuthConfig) Validate(audiences provisioner.Audiences) error {
|
||||
if c == nil {
|
||||
return errors.New("authority cannot be undefined")
|
||||
}
|
||||
// TLSOptions is an alias to support older APIs.
|
||||
type TLSOptions = config.TLSOptions
|
||||
|
||||
// Initialize required fields.
|
||||
c.init()
|
||||
// CipherSuites is an alias to support older APIs.
|
||||
type CipherSuites = config.CipherSuites
|
||||
|
||||
// Check that only one K8sSA is enabled
|
||||
var k8sCount int
|
||||
for _, p := range c.Provisioners {
|
||||
if p.GetType() == provisioner.TypeK8sSA {
|
||||
k8sCount++
|
||||
}
|
||||
}
|
||||
if k8sCount > 1 {
|
||||
return errors.New("cannot have more than one kubernetes service account provisioner")
|
||||
}
|
||||
// SSH
|
||||
|
||||
if c.Backdate.Duration < 0 {
|
||||
return errors.New("authority.backdate cannot be less than 0")
|
||||
}
|
||||
// SSHConfig is an alias to support older APIs.
|
||||
type SSHConfig = config.SSHConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
// Bastion is an alias to support older APIs.
|
||||
type Bastion = config.Bastion
|
||||
|
||||
// LoadConfiguration parses the given filename in JSON format and returns the
|
||||
// configuration struct.
|
||||
func LoadConfiguration(filename string) (*Config, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening %s", filename)
|
||||
}
|
||||
defer f.Close()
|
||||
// HostTag is an alias to support older APIs.
|
||||
type HostTag = config.HostTag
|
||||
|
||||
var c Config
|
||||
if err := json.NewDecoder(f).Decode(&c); err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing %s", filename)
|
||||
}
|
||||
// Host is an alias to support older APIs.
|
||||
type Host = config.Host
|
||||
|
||||
c.init()
|
||||
// SSHPublicKey is an alias to support older APIs.
|
||||
type SSHPublicKey = config.SSHPublicKey
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// initializes the minimal configuration required to create an authority. This
|
||||
// is mainly used on embedded authorities.
|
||||
func (c *Config) init() {
|
||||
if c.DNSNames == nil {
|
||||
c.DNSNames = []string{"localhost", "127.0.0.1", "::1"}
|
||||
}
|
||||
if c.TLS == nil {
|
||||
c.TLS = &DefaultTLSOptions
|
||||
}
|
||||
if c.AuthorityConfig == nil {
|
||||
c.AuthorityConfig = &AuthConfig{}
|
||||
}
|
||||
c.AuthorityConfig.init()
|
||||
}
|
||||
|
||||
// Save saves the configuration to the given filename.
|
||||
func (c *Config) Save(filename string) error {
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error opening %s", filename)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", "\t")
|
||||
return errors.Wrapf(enc.Encode(c), "error writing %s", filename)
|
||||
}
|
||||
|
||||
// Validate validates the configuration.
|
||||
func (c *Config) Validate() error {
|
||||
switch {
|
||||
case c.Address == "":
|
||||
return errors.New("address cannot be empty")
|
||||
|
||||
case len(c.DNSNames) == 0:
|
||||
return errors.New("dnsNames cannot be empty")
|
||||
}
|
||||
|
||||
// Options holds the RA/CAS configuration.
|
||||
ra := c.AuthorityConfig.Options
|
||||
// The default RA/CAS requires root, crt and key.
|
||||
if ra.Is(cas.SoftCAS) {
|
||||
switch {
|
||||
case c.Root.HasEmpties():
|
||||
return errors.New("root cannot be empty")
|
||||
case c.IntermediateCert == "":
|
||||
return errors.New("crt cannot be empty")
|
||||
case c.IntermediateKey == "":
|
||||
return errors.New("key cannot be empty")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate address (a port is required)
|
||||
if _, _, err := net.SplitHostPort(c.Address); err != nil {
|
||||
return errors.Errorf("invalid address %s", c.Address)
|
||||
}
|
||||
|
||||
// Validate insecure address if it is configured
|
||||
if c.InsecureAddress != "" {
|
||||
if _, _, err := net.SplitHostPort(c.InsecureAddress); err != nil {
|
||||
return errors.Errorf("invalid address %s", c.InsecureAddress)
|
||||
}
|
||||
}
|
||||
|
||||
if c.TLS == nil {
|
||||
c.TLS = &DefaultTLSOptions
|
||||
} else {
|
||||
if len(c.TLS.CipherSuites) == 0 {
|
||||
c.TLS.CipherSuites = DefaultTLSOptions.CipherSuites
|
||||
}
|
||||
if c.TLS.MaxVersion == 0 {
|
||||
c.TLS.MaxVersion = DefaultTLSOptions.MaxVersion
|
||||
}
|
||||
if c.TLS.MinVersion == 0 {
|
||||
c.TLS.MinVersion = c.TLS.MaxVersion
|
||||
}
|
||||
if c.TLS.MinVersion > c.TLS.MaxVersion {
|
||||
return errors.New("tls minVersion cannot exceed tls maxVersion")
|
||||
}
|
||||
c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation
|
||||
}
|
||||
|
||||
// Validate KMS options, nil is ok.
|
||||
if err := c.KMS.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate RA/CAS options, nil is ok.
|
||||
if err := ra.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate ssh: nil is ok
|
||||
if err := c.SSH.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate templates: nil is ok
|
||||
if err := c.Templates.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.AuthorityConfig.Validate(c.getAudiences())
|
||||
}
|
||||
|
||||
// getAudiences returns the legacy and possible urls without the ports that will
|
||||
// be used as the default provisioner audiences. The CA might have proxies in
|
||||
// front so we cannot rely on the port.
|
||||
func (c *Config) getAudiences() provisioner.Audiences {
|
||||
audiences := provisioner.Audiences{
|
||||
Sign: []string{legacyAuthority},
|
||||
Revoke: []string{legacyAuthority},
|
||||
SSHSign: []string{},
|
||||
SSHRevoke: []string{},
|
||||
SSHRenew: []string{},
|
||||
}
|
||||
|
||||
for _, name := range c.DNSNames {
|
||||
audiences.Sign = append(audiences.Sign,
|
||||
fmt.Sprintf("https://%s/1.0/sign", name),
|
||||
fmt.Sprintf("https://%s/sign", name),
|
||||
fmt.Sprintf("https://%s/1.0/ssh/sign", name),
|
||||
fmt.Sprintf("https://%s/ssh/sign", name))
|
||||
audiences.Revoke = append(audiences.Revoke,
|
||||
fmt.Sprintf("https://%s/1.0/revoke", name),
|
||||
fmt.Sprintf("https://%s/revoke", name))
|
||||
audiences.SSHSign = append(audiences.SSHSign,
|
||||
fmt.Sprintf("https://%s/1.0/ssh/sign", name),
|
||||
fmt.Sprintf("https://%s/ssh/sign", name),
|
||||
fmt.Sprintf("https://%s/1.0/sign", name),
|
||||
fmt.Sprintf("https://%s/sign", name))
|
||||
audiences.SSHRevoke = append(audiences.SSHRevoke,
|
||||
fmt.Sprintf("https://%s/1.0/ssh/revoke", name),
|
||||
fmt.Sprintf("https://%s/ssh/revoke", name))
|
||||
audiences.SSHRenew = append(audiences.SSHRenew,
|
||||
fmt.Sprintf("https://%s/1.0/ssh/renew", name),
|
||||
fmt.Sprintf("https://%s/ssh/renew", name))
|
||||
audiences.SSHRekey = append(audiences.SSHRekey,
|
||||
fmt.Sprintf("https://%s/1.0/ssh/rekey", name),
|
||||
fmt.Sprintf("https://%s/ssh/rekey", name))
|
||||
}
|
||||
|
||||
return audiences
|
||||
}
|
||||
// SSHKeys is an alias to support older APIs.
|
||||
type SSHKeys = config.SSHKeys
|
||||
|
|
294
authority/config/config.go
Normal file
294
authority/config/config.go
Normal file
|
@ -0,0 +1,294 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
cas "github.com/smallstep/certificates/cas/apiv1"
|
||||
"github.com/smallstep/certificates/db"
|
||||
kms "github.com/smallstep/certificates/kms/apiv1"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
const (
|
||||
legacyAuthority = "step-certificate-authority"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultBackdate length of time to backdate certificates to avoid
|
||||
// clock skew validation issues.
|
||||
DefaultBackdate = time.Minute
|
||||
// DefaultDisableRenewal disables renewals per provisioner.
|
||||
DefaultDisableRenewal = false
|
||||
// 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.
|
||||
GlobalProvisionerClaims = provisioner.Claims{
|
||||
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, // TLS certs
|
||||
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
||||
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
||||
DisableRenewal: &DefaultDisableRenewal,
|
||||
MinUserSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // User SSH certs
|
||||
MaxUserSSHDur: &provisioner.Duration{Duration: 24 * time.Hour},
|
||||
DefaultUserSSHDur: &provisioner.Duration{Duration: 16 * time.Hour},
|
||||
MinHostSSHDur: &provisioner.Duration{Duration: 5 * time.Minute}, // Host SSH certs
|
||||
MaxHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour},
|
||||
DefaultHostSSHDur: &provisioner.Duration{Duration: 30 * 24 * time.Hour},
|
||||
EnableSSHCA: &DefaultEnableSSHCA,
|
||||
}
|
||||
)
|
||||
|
||||
// Config represents the CA configuration and it's mapped to a JSON object.
|
||||
type Config struct {
|
||||
Root multiString `json:"root"`
|
||||
FederatedRoots []string `json:"federatedRoots"`
|
||||
IntermediateCert string `json:"crt"`
|
||||
IntermediateKey string `json:"key"`
|
||||
Address string `json:"address"`
|
||||
InsecureAddress string `json:"insecureAddress"`
|
||||
DNSNames []string `json:"dnsNames"`
|
||||
KMS *kms.Options `json:"kms,omitempty"`
|
||||
SSH *SSHConfig `json:"ssh,omitempty"`
|
||||
Logger json.RawMessage `json:"logger,omitempty"`
|
||||
DB *db.Config `json:"db,omitempty"`
|
||||
Monitoring json.RawMessage `json:"monitoring,omitempty"`
|
||||
AuthorityConfig *AuthConfig `json:"authority,omitempty"`
|
||||
TLS *TLSOptions `json:"tls,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Templates *templates.Templates `json:"templates,omitempty"`
|
||||
}
|
||||
|
||||
// ASN1DN contains ASN1.DN attributes that are used in Subject and Issuer
|
||||
// x509 Certificate blocks.
|
||||
type ASN1DN struct {
|
||||
Country string `json:"country,omitempty"`
|
||||
Organization string `json:"organization,omitempty"`
|
||||
OrganizationalUnit string `json:"organizationalUnit,omitempty"`
|
||||
Locality string `json:"locality,omitempty"`
|
||||
Province string `json:"province,omitempty"`
|
||||
StreetAddress string `json:"streetAddress,omitempty"`
|
||||
CommonName string `json:"commonName,omitempty"`
|
||||
}
|
||||
|
||||
// AuthConfig represents the configuration options for the authority. An
|
||||
// underlaying registration authority can also be configured using the
|
||||
// cas.Options.
|
||||
type AuthConfig struct {
|
||||
*cas.Options
|
||||
AuthorityID string `json:"authorityID,omitempty"`
|
||||
Provisioners provisioner.List `json:"provisioners"`
|
||||
Admins []*linkedca.Admin `json:"-"`
|
||||
Template *ASN1DN `json:"template,omitempty"`
|
||||
Claims *provisioner.Claims `json:"claims,omitempty"`
|
||||
DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"`
|
||||
Backdate *provisioner.Duration `json:"backdate,omitempty"`
|
||||
EnableAdmin bool `json:"enableAdmin,omitempty"`
|
||||
}
|
||||
|
||||
// init initializes the required fields in the AuthConfig if they are not
|
||||
// provided.
|
||||
func (c *AuthConfig) init() {
|
||||
if c.Provisioners == nil {
|
||||
c.Provisioners = provisioner.List{}
|
||||
}
|
||||
if c.Template == nil {
|
||||
c.Template = &ASN1DN{}
|
||||
}
|
||||
if c.Backdate == nil {
|
||||
c.Backdate = &provisioner.Duration{
|
||||
Duration: DefaultBackdate,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the authority configuration.
|
||||
func (c *AuthConfig) Validate(audiences provisioner.Audiences) error {
|
||||
if c == nil {
|
||||
return errors.New("authority cannot be undefined")
|
||||
}
|
||||
|
||||
// Initialize required fields.
|
||||
c.init()
|
||||
|
||||
// Check that only one K8sSA is enabled
|
||||
var k8sCount int
|
||||
for _, p := range c.Provisioners {
|
||||
if p.GetType() == provisioner.TypeK8sSA {
|
||||
k8sCount++
|
||||
}
|
||||
}
|
||||
if k8sCount > 1 {
|
||||
return errors.New("cannot have more than one kubernetes service account provisioner")
|
||||
}
|
||||
|
||||
if c.Backdate.Duration < 0 {
|
||||
return errors.New("authority.backdate cannot be less than 0")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadConfiguration parses the given filename in JSON format and returns the
|
||||
// configuration struct.
|
||||
func LoadConfiguration(filename string) (*Config, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening %s", filename)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var c Config
|
||||
if err := json.NewDecoder(f).Decode(&c); err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing %s", filename)
|
||||
}
|
||||
|
||||
c.Init()
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// Init initializes the minimal configuration required to create an authority. This
|
||||
// is mainly used on embedded authorities.
|
||||
func (c *Config) Init() {
|
||||
if c.DNSNames == nil {
|
||||
c.DNSNames = []string{"localhost", "127.0.0.1", "::1"}
|
||||
}
|
||||
if c.TLS == nil {
|
||||
c.TLS = &DefaultTLSOptions
|
||||
}
|
||||
if c.AuthorityConfig == nil {
|
||||
c.AuthorityConfig = &AuthConfig{}
|
||||
}
|
||||
c.AuthorityConfig.init()
|
||||
}
|
||||
|
||||
// Save saves the configuration to the given filename.
|
||||
func (c *Config) Save(filename string) error {
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error opening %s", filename)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", "\t")
|
||||
return errors.Wrapf(enc.Encode(c), "error writing %s", filename)
|
||||
}
|
||||
|
||||
// Validate validates the configuration.
|
||||
func (c *Config) Validate() error {
|
||||
switch {
|
||||
case c.Address == "":
|
||||
return errors.New("address cannot be empty")
|
||||
|
||||
case len(c.DNSNames) == 0:
|
||||
return errors.New("dnsNames cannot be empty")
|
||||
}
|
||||
|
||||
// Options holds the RA/CAS configuration.
|
||||
ra := c.AuthorityConfig.Options
|
||||
// The default RA/CAS requires root, crt and key.
|
||||
if ra.Is(cas.SoftCAS) {
|
||||
switch {
|
||||
case c.Root.HasEmpties():
|
||||
return errors.New("root cannot be empty")
|
||||
case c.IntermediateCert == "":
|
||||
return errors.New("crt cannot be empty")
|
||||
case c.IntermediateKey == "":
|
||||
return errors.New("key cannot be empty")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate address (a port is required)
|
||||
if _, _, err := net.SplitHostPort(c.Address); err != nil {
|
||||
return errors.Errorf("invalid address %s", c.Address)
|
||||
}
|
||||
|
||||
if c.TLS == nil {
|
||||
c.TLS = &DefaultTLSOptions
|
||||
} else {
|
||||
if len(c.TLS.CipherSuites) == 0 {
|
||||
c.TLS.CipherSuites = DefaultTLSOptions.CipherSuites
|
||||
}
|
||||
if c.TLS.MaxVersion == 0 {
|
||||
c.TLS.MaxVersion = DefaultTLSOptions.MaxVersion
|
||||
}
|
||||
if c.TLS.MinVersion == 0 {
|
||||
c.TLS.MinVersion = c.TLS.MaxVersion
|
||||
}
|
||||
if c.TLS.MinVersion > c.TLS.MaxVersion {
|
||||
return errors.New("tls minVersion cannot exceed tls maxVersion")
|
||||
}
|
||||
c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation
|
||||
}
|
||||
|
||||
// Validate KMS options, nil is ok.
|
||||
if err := c.KMS.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate RA/CAS options, nil is ok.
|
||||
if err := ra.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate ssh: nil is ok
|
||||
if err := c.SSH.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate templates: nil is ok
|
||||
if err := c.Templates.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.AuthorityConfig.Validate(c.GetAudiences())
|
||||
}
|
||||
|
||||
// GetAudiences returns the legacy and possible urls without the ports that will
|
||||
// be used as the default provisioner audiences. The CA might have proxies in
|
||||
// front so we cannot rely on the port.
|
||||
func (c *Config) GetAudiences() provisioner.Audiences {
|
||||
audiences := provisioner.Audiences{
|
||||
Sign: []string{legacyAuthority},
|
||||
Revoke: []string{legacyAuthority},
|
||||
SSHSign: []string{},
|
||||
SSHRevoke: []string{},
|
||||
SSHRenew: []string{},
|
||||
}
|
||||
|
||||
for _, name := range c.DNSNames {
|
||||
audiences.Sign = append(audiences.Sign,
|
||||
fmt.Sprintf("https://%s/1.0/sign", name),
|
||||
fmt.Sprintf("https://%s/sign", name),
|
||||
fmt.Sprintf("https://%s/1.0/ssh/sign", name),
|
||||
fmt.Sprintf("https://%s/ssh/sign", name))
|
||||
audiences.Revoke = append(audiences.Revoke,
|
||||
fmt.Sprintf("https://%s/1.0/revoke", name),
|
||||
fmt.Sprintf("https://%s/revoke", name))
|
||||
audiences.SSHSign = append(audiences.SSHSign,
|
||||
fmt.Sprintf("https://%s/1.0/ssh/sign", name),
|
||||
fmt.Sprintf("https://%s/ssh/sign", name),
|
||||
fmt.Sprintf("https://%s/1.0/sign", name),
|
||||
fmt.Sprintf("https://%s/sign", name))
|
||||
audiences.SSHRevoke = append(audiences.SSHRevoke,
|
||||
fmt.Sprintf("https://%s/1.0/ssh/revoke", name),
|
||||
fmt.Sprintf("https://%s/ssh/revoke", name))
|
||||
audiences.SSHRenew = append(audiences.SSHRenew,
|
||||
fmt.Sprintf("https://%s/1.0/ssh/renew", name),
|
||||
fmt.Sprintf("https://%s/ssh/renew", name))
|
||||
audiences.SSHRekey = append(audiences.SSHRekey,
|
||||
fmt.Sprintf("https://%s/1.0/ssh/rekey", name),
|
||||
fmt.Sprintf("https://%s/ssh/rekey", name))
|
||||
}
|
||||
|
||||
return audiences
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package authority
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -8,12 +8,14 @@ import (
|
|||
"github.com/smallstep/assert"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"go.step.sm/crypto/jose"
|
||||
|
||||
_ "github.com/smallstep/certificates/cas"
|
||||
)
|
||||
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
maxjwk, err := jose.ReadKey("testdata/secrets/max_pub.jwk")
|
||||
maxjwk, err := jose.ReadKey("../testdata/secrets/max_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
clijwk, err := jose.ReadKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||
clijwk, err := jose.ReadKey("../testdata/secrets/step_cli_key_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
ac := &AuthConfig{
|
||||
Provisioners: provisioner.List{
|
||||
|
@ -39,9 +41,9 @@ func TestConfigValidate(t *testing.T) {
|
|||
"empty-address": func(t *testing.T) ConfigValidateTest {
|
||||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Root: []string{"testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "testdata/secrets/intermediate_ca_key",
|
||||
Root: []string{"../testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "../testdata/secrets/intermediate_ca_key",
|
||||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
|
@ -53,9 +55,9 @@ func TestConfigValidate(t *testing.T) {
|
|||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Address: "127.0.0.1",
|
||||
Root: []string{"testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "testdata/secrets/intermediate_ca_key",
|
||||
Root: []string{"../testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "../testdata/secrets/intermediate_ca_key",
|
||||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
|
@ -67,8 +69,8 @@ func TestConfigValidate(t *testing.T) {
|
|||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Address: "127.0.0.1:443",
|
||||
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "testdata/secrets/intermediate_ca_key",
|
||||
IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "../testdata/secrets/intermediate_ca_key",
|
||||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
|
@ -80,8 +82,8 @@ func TestConfigValidate(t *testing.T) {
|
|||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Address: "127.0.0.1:443",
|
||||
Root: []string{"testdata/secrets/root_ca.crt"},
|
||||
IntermediateKey: "testdata/secrets/intermediate_ca_key",
|
||||
Root: []string{"../testdata/secrets/root_ca.crt"},
|
||||
IntermediateKey: "../testdata/secrets/intermediate_ca_key",
|
||||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
|
@ -93,8 +95,8 @@ func TestConfigValidate(t *testing.T) {
|
|||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Address: "127.0.0.1:443",
|
||||
Root: []string{"testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
|
||||
Root: []string{"../testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
|
||||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
|
@ -106,9 +108,9 @@ func TestConfigValidate(t *testing.T) {
|
|||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Address: "127.0.0.1:443",
|
||||
Root: []string{"testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "testdata/secrets/intermediate_ca_key",
|
||||
Root: []string{"../testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "../testdata/secrets/intermediate_ca_key",
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
},
|
||||
|
@ -119,9 +121,9 @@ func TestConfigValidate(t *testing.T) {
|
|||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Address: "127.0.0.1:443",
|
||||
Root: []string{"testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "testdata/secrets/intermediate_ca_key",
|
||||
Root: []string{"../testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "../testdata/secrets/intermediate_ca_key",
|
||||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
|
@ -133,9 +135,9 @@ func TestConfigValidate(t *testing.T) {
|
|||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Address: "127.0.0.1:443",
|
||||
Root: []string{"testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "testdata/secrets/intermediate_ca_key",
|
||||
Root: []string{"../testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "../testdata/secrets/intermediate_ca_key",
|
||||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
|
@ -148,9 +150,9 @@ func TestConfigValidate(t *testing.T) {
|
|||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Address: "127.0.0.1:443",
|
||||
Root: []string{"testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "testdata/secrets/intermediate_ca_key",
|
||||
Root: []string{"../testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "../testdata/secrets/intermediate_ca_key",
|
||||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
|
@ -177,9 +179,9 @@ func TestConfigValidate(t *testing.T) {
|
|||
return ConfigValidateTest{
|
||||
config: &Config{
|
||||
Address: "127.0.0.1:443",
|
||||
Root: []string{"testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "testdata/secrets/intermediate_ca_key",
|
||||
Root: []string{"../testdata/secrets/root_ca.crt"},
|
||||
IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
|
||||
IntermediateKey: "../testdata/secrets/intermediate_ca_key",
|
||||
DNSNames: []string{"test.smallstep.com"},
|
||||
Password: "pass",
|
||||
AuthorityConfig: ac,
|
||||
|
@ -207,6 +209,8 @@ func TestConfigValidate(t *testing.T) {
|
|||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
fmt.Printf("tc.tls = %+v\n", tc.tls)
|
||||
fmt.Printf("*tc.config.TLS = %+v\n", *tc.config.TLS)
|
||||
assert.Equals(t, *tc.config.TLS, tc.tls)
|
||||
}
|
||||
}
|
||||
|
@ -224,9 +228,9 @@ func TestAuthConfigValidate(t *testing.T) {
|
|||
CommonName: "test",
|
||||
}
|
||||
|
||||
maxjwk, err := jose.ReadKey("testdata/secrets/max_pub.jwk")
|
||||
maxjwk, err := jose.ReadKey("../testdata/secrets/max_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
clijwk, err := jose.ReadKey("testdata/secrets/step_cli_key_pub.jwk")
|
||||
clijwk, err := jose.ReadKey("../testdata/secrets/step_cli_key_pub.jwk")
|
||||
assert.FatalError(t, err)
|
||||
p := provisioner.List{
|
||||
&provisioner.JWK{
|
94
authority/config/ssh.go
Normal file
94
authority/config/ssh.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"go.step.sm/crypto/jose"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// SSHConfig contains the user and host keys.
|
||||
type SSHConfig struct {
|
||||
HostKey string `json:"hostKey"`
|
||||
UserKey string `json:"userKey"`
|
||||
Keys []*SSHPublicKey `json:"keys,omitempty"`
|
||||
AddUserPrincipal string `json:"addUserPrincipal,omitempty"`
|
||||
AddUserCommand string `json:"addUserCommand,omitempty"`
|
||||
Bastion *Bastion `json:"bastion,omitempty"`
|
||||
}
|
||||
|
||||
// Bastion contains the custom properties used on bastion.
|
||||
type Bastion struct {
|
||||
Hostname string `json:"hostname"`
|
||||
User string `json:"user,omitempty"`
|
||||
Port string `json:"port,omitempty"`
|
||||
Command string `json:"cmd,omitempty"`
|
||||
Flags string `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
// HostTag are tagged with k,v pairs. These tags are how a user is ultimately
|
||||
// associated with a host.
|
||||
type HostTag struct {
|
||||
ID string
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Host defines expected attributes for an ssh host.
|
||||
type Host struct {
|
||||
HostID string `json:"hid"`
|
||||
HostTags []HostTag `json:"host_tags"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
// Validate checks the fields in SSHConfig.
|
||||
func (c *SSHConfig) Validate() error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
for _, k := range c.Keys {
|
||||
if err := k.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SSHPublicKey contains a public key used by federated CAs to keep old signing
|
||||
// keys for this ca.
|
||||
type SSHPublicKey struct {
|
||||
Type string `json:"type"`
|
||||
Federated bool `json:"federated"`
|
||||
Key jose.JSONWebKey `json:"key"`
|
||||
publicKey ssh.PublicKey
|
||||
}
|
||||
|
||||
// Validate checks the fields in SSHPublicKey.
|
||||
func (k *SSHPublicKey) Validate() error {
|
||||
switch {
|
||||
case k.Type == "":
|
||||
return errors.New("type cannot be empty")
|
||||
case k.Type != provisioner.SSHHostCert && k.Type != provisioner.SSHUserCert:
|
||||
return errors.Errorf("invalid type %s, it must be user or host", k.Type)
|
||||
case !k.Key.IsPublic():
|
||||
return errors.New("invalid key type, it must be a public key")
|
||||
}
|
||||
|
||||
key, err := ssh.NewPublicKey(k.Key.Key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating ssh key")
|
||||
}
|
||||
k.publicKey = key
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublicKey returns the ssh public key.
|
||||
func (k *SSHPublicKey) PublicKey() ssh.PublicKey {
|
||||
return k.publicKey
|
||||
}
|
||||
|
||||
// SSHKeys represents the SSH User and Host public keys.
|
||||
type SSHKeys struct {
|
||||
UserKeys []ssh.PublicKey
|
||||
HostKeys []ssh.PublicKey
|
||||
}
|
73
authority/config/ssh_test.go
Normal file
73
authority/config/ssh_test.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/smallstep/assert"
|
||||
"go.step.sm/crypto/jose"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestSSHPublicKey_Validate(t *testing.T) {
|
||||
key, err := jose.GenerateJWK("EC", "P-256", "", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type fields struct {
|
||||
Type string
|
||||
Federated bool
|
||||
Key jose.JSONWebKey
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantErr bool
|
||||
}{
|
||||
{"user", fields{"user", true, key.Public()}, false},
|
||||
{"host", fields{"host", false, key.Public()}, false},
|
||||
{"empty", fields{"", true, key.Public()}, true},
|
||||
{"badType", fields{"bad", false, key.Public()}, true},
|
||||
{"badKey", fields{"user", false, *key}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
k := &SSHPublicKey{
|
||||
Type: tt.fields.Type,
|
||||
Federated: tt.fields.Federated,
|
||||
Key: tt.fields.Key,
|
||||
}
|
||||
if err := k.Validate(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("SSHPublicKey.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHPublicKey_PublicKey(t *testing.T) {
|
||||
key, err := jose.GenerateJWK("EC", "P-256", "", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
pub, err := ssh.NewPublicKey(key.Public().Key)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type fields struct {
|
||||
publicKey ssh.PublicKey
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want ssh.PublicKey
|
||||
}{
|
||||
{"ok", fields{pub}, pub},
|
||||
{"nil", fields{nil}, nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
k := &SSHPublicKey{
|
||||
publicKey: tt.fields.publicKey,
|
||||
}
|
||||
if got := k.PublicKey(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("SSHPublicKey.PublicKey() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package authority
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
@ -34,6 +34,18 @@ var (
|
|||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
|
||||
}
|
||||
// DefaultTLSOptions represents the default TLS version as well as the cipher
|
||||
// suites used in the TLS certificates.
|
||||
DefaultTLSOptions = TLSOptions{
|
||||
CipherSuites: CipherSuites{
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||
},
|
||||
MinVersion: 1.2,
|
||||
MaxVersion: 1.2,
|
||||
Renegotiation: false,
|
||||
}
|
||||
)
|
||||
|
||||
// TLSVersion represents a TLS version number.
|
|
@ -1,4 +1,4 @@
|
|||
package authority
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
|
@ -1,4 +1,4 @@
|
|||
package authority
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
|||
package authority
|
||||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
|
@ -7,6 +7,8 @@ import (
|
|||
"encoding/pem"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/cas"
|
||||
casapi "github.com/smallstep/certificates/cas/apiv1"
|
||||
|
@ -20,7 +22,7 @@ type Option func(*Authority) error
|
|||
|
||||
// WithConfig replaces the current config with the given one. No validation is
|
||||
// performed in the given value.
|
||||
func WithConfig(config *Config) Option {
|
||||
func WithConfig(config *config.Config) Option {
|
||||
return func(a *Authority) error {
|
||||
a.config = config
|
||||
return nil
|
||||
|
@ -31,7 +33,7 @@ func WithConfig(config *Config) Option {
|
|||
// the current one. No validation is performed in the given configuration.
|
||||
func WithConfigFile(filename string) Option {
|
||||
return func(a *Authority) (err error) {
|
||||
a.config, err = LoadConfiguration(filename)
|
||||
a.config, err = config.LoadConfiguration(filename)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +58,7 @@ func WithGetIdentityFunc(fn func(ctx context.Context, p provisioner.Interface, e
|
|||
|
||||
// WithSSHBastionFunc sets a custom function to get the bastion for a
|
||||
// given user-host pair.
|
||||
func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*Bastion, error)) Option {
|
||||
func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*config.Bastion, error)) Option {
|
||||
return func(a *Authority) error {
|
||||
a.sshBastionFunc = fn
|
||||
return nil
|
||||
|
@ -65,7 +67,7 @@ func WithSSHBastionFunc(fn func(ctx context.Context, user, host string) (*Bastio
|
|||
|
||||
// WithSSHGetHosts sets a custom function to get the bastion for a
|
||||
// given user-host pair.
|
||||
func WithSSHGetHosts(fn func(ctx context.Context, cert *x509.Certificate) ([]Host, error)) Option {
|
||||
func WithSSHGetHosts(fn func(ctx context.Context, cert *x509.Certificate) ([]config.Host, error)) Option {
|
||||
return func(a *Authority) error {
|
||||
a.sshGetHostsFunc = fn
|
||||
return nil
|
||||
|
@ -186,6 +188,14 @@ func WithX509FederatedBundle(pemCerts []byte) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithAdminDB is an option to set the database backing the admin APIs.
|
||||
func WithAdminDB(db admin.DB) Option {
|
||||
return func(a *Authority) error {
|
||||
a.adminDB = db
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
|
||||
var block *pem.Block
|
||||
var certs []*x509.Certificate
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
// provisioning flow.
|
||||
type ACME struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
ForceCN bool `json:"forceCN,omitempty"`
|
||||
|
@ -23,6 +24,15 @@ type ACME struct {
|
|||
|
||||
// GetID returns the provisioner unique identifier.
|
||||
func (p ACME) GetID() string {
|
||||
if p.ID != "" {
|
||||
return p.ID
|
||||
}
|
||||
return p.GetIDForToken()
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (p *ACME) GetIDForToken() string {
|
||||
return "acme/" + p.Name
|
||||
}
|
||||
|
||||
|
@ -95,7 +105,7 @@ func (p *ACME) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e
|
|||
// certificate was configured to allow renewals.
|
||||
func (p *ACME) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||
if p.claimer.IsDisableRenewal() {
|
||||
return errs.Unauthorized("acme.AuthorizeRenew; renew is disabled for acme provisioner %s", p.GetID())
|
||||
return errs.Unauthorized("acme.AuthorizeRenew; renew is disabled for acme provisioner '%s'", p.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func TestACME_Init(t *testing.T) {
|
|||
"fail-bad-claims": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &ACME{Name: "foo", Type: "bar", Claims: &Claims{DefaultTLSDur: &Duration{0}}},
|
||||
err: errors.New("claims: DefaultTLSCertDuration must be greater than 0"),
|
||||
err: errors.New("claims: MinTLSCertDuration must be greater than 0"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) ProvisionerValidateTest {
|
||||
|
@ -110,7 +110,7 @@ func TestACME_AuthorizeRenew(t *testing.T) {
|
|||
p: p,
|
||||
cert: &x509.Certificate{},
|
||||
code: http.StatusUnauthorized,
|
||||
err: errors.Errorf("acme.AuthorizeRenew; renew is disabled for acme provisioner %s", p.GetID()),
|
||||
err: errors.Errorf("acme.AuthorizeRenew; renew is disabled for acme provisioner '%s'", p.GetName()),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
|
|
|
@ -252,6 +252,7 @@ type awsInstanceIdentityDocument struct {
|
|||
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
|
||||
type AWS struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Accounts []string `json:"accounts"`
|
||||
|
@ -269,6 +270,15 @@ type AWS struct {
|
|||
|
||||
// GetID returns the provisioner unique identifier.
|
||||
func (p *AWS) GetID() string {
|
||||
if p.ID != "" {
|
||||
return p.ID
|
||||
}
|
||||
return p.GetIDForToken()
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (p *AWS) GetIDForToken() string {
|
||||
return "aws/" + p.Name
|
||||
}
|
||||
|
||||
|
@ -286,7 +296,7 @@ func (p *AWS) GetTokenID(token string) (string, error) {
|
|||
}
|
||||
|
||||
// Use provisioner + instance-id as the identifier.
|
||||
unique := fmt.Sprintf("%s.%s", p.GetID(), payload.document.InstanceID)
|
||||
unique := fmt.Sprintf("%s.%s", p.GetIDForToken(), payload.document.InstanceID)
|
||||
sum := sha256.Sum256([]byte(unique))
|
||||
return strings.ToLower(hex.EncodeToString(sum[:])), nil
|
||||
}
|
||||
|
@ -334,7 +344,7 @@ func (p *AWS) GetIdentityToken(subject, caURL string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
audience, err := generateSignAudience(caURL, p.GetID())
|
||||
audience, err := generateSignAudience(caURL, p.GetIDForToken())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -342,7 +352,7 @@ func (p *AWS) GetIdentityToken(subject, caURL string) (string, error) {
|
|||
// Create unique ID for Trust On First Use (TOFU). Only the first instance
|
||||
// per provisioner is allowed as we don't have a way to trust the given
|
||||
// sans.
|
||||
unique := fmt.Sprintf("%s.%s", p.GetID(), idoc.InstanceID)
|
||||
unique := fmt.Sprintf("%s.%s", p.GetIDForToken(), idoc.InstanceID)
|
||||
sum := sha256.Sum256([]byte(unique))
|
||||
|
||||
// Create a JWT from the identity document
|
||||
|
@ -397,7 +407,7 @@ func (p *AWS) Init(config Config) (err error) {
|
|||
if p.config, err = newAWSConfig(p.IIDRoots); err != nil {
|
||||
return err
|
||||
}
|
||||
p.audiences = config.Audiences.WithFragment(p.GetID())
|
||||
p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
|
||||
|
||||
// validate IMDS versions
|
||||
if len(p.IMDSVersions) == 0 {
|
||||
|
@ -474,7 +484,7 @@ func (p *AWS) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
// certificate was configured to allow renewals.
|
||||
func (p *AWS) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||
if p.claimer.IsDisableRenewal() {
|
||||
return errs.Unauthorized("aws.AuthorizeRenew; renew is disabled for aws provisioner %s", p.GetID())
|
||||
return errs.Unauthorized("aws.AuthorizeRenew; renew is disabled for aws provisioner '%s'", p.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -687,7 +697,7 @@ func (p *AWS) authorizeToken(token string) (*awsPayload, error) {
|
|||
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||
func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errs.Unauthorized("aws.AuthorizeSSHSign; ssh ca is disabled for aws provisioner %s", p.GetID())
|
||||
return nil, errs.Unauthorized("aws.AuthorizeSSHSign; ssh ca is disabled for aws provisioner '%s'", p.GetName())
|
||||
}
|
||||
claims, err := p.authorizeToken(token)
|
||||
if err != nil {
|
||||
|
|
|
@ -84,6 +84,7 @@ type azurePayload struct {
|
|||
// and https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
|
||||
type Azure struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
TenantID string `json:"tenantID"`
|
||||
|
@ -101,6 +102,15 @@ type Azure struct {
|
|||
|
||||
// GetID returns the provisioner unique identifier.
|
||||
func (p *Azure) GetID() string {
|
||||
if p.ID != "" {
|
||||
return p.ID
|
||||
}
|
||||
return p.GetIDForToken()
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (p *Azure) GetIDForToken() string {
|
||||
return p.TenantID
|
||||
}
|
||||
|
||||
|
@ -324,7 +334,7 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption,
|
|||
// certificate was configured to allow renewals.
|
||||
func (p *Azure) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||
if p.claimer.IsDisableRenewal() {
|
||||
return errs.Unauthorized("azure.AuthorizeRenew; renew is disabled for azure provisioner %s", p.GetID())
|
||||
return errs.Unauthorized("azure.AuthorizeRenew; renew is disabled for azure provisioner '%s'", p.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -332,7 +342,7 @@ func (p *Azure) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) erro
|
|||
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||
func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errs.Unauthorized("azure.AuthorizeSSHSign; sshCA is disabled for provisioner %s", p.GetID())
|
||||
return nil, errs.Unauthorized("azure.AuthorizeSSHSign; sshCA is disabled for provisioner '%s'", p.GetName())
|
||||
}
|
||||
|
||||
_, name, _, err := p.authorizeToken(token)
|
||||
|
|
|
@ -71,6 +71,9 @@ func (c *Claimer) DefaultTLSCertDuration() time.Duration {
|
|||
// minimum from the authority configuration will be used.
|
||||
func (c *Claimer) MinTLSCertDuration() time.Duration {
|
||||
if c.claims == nil || c.claims.MinTLSDur == nil {
|
||||
if c.claims != nil && c.claims.DefaultTLSDur != nil && c.claims.DefaultTLSDur.Duration < c.global.MinTLSDur.Duration {
|
||||
return c.claims.DefaultTLSDur.Duration
|
||||
}
|
||||
return c.global.MinTLSDur.Duration
|
||||
}
|
||||
return c.claims.MinTLSDur.Duration
|
||||
|
@ -81,6 +84,9 @@ func (c *Claimer) MinTLSCertDuration() time.Duration {
|
|||
// maximum from the authority configuration will be used.
|
||||
func (c *Claimer) MaxTLSCertDuration() time.Duration {
|
||||
if c.claims == nil || c.claims.MaxTLSDur == nil {
|
||||
if c.claims != nil && c.claims.DefaultTLSDur != nil && c.claims.DefaultTLSDur.Duration > c.global.MaxTLSDur.Duration {
|
||||
return c.claims.DefaultTLSDur.Duration
|
||||
}
|
||||
return c.global.MaxTLSDur.Duration
|
||||
}
|
||||
return c.claims.MaxTLSDur.Duration
|
||||
|
@ -126,6 +132,9 @@ func (c *Claimer) DefaultUserSSHCertDuration() time.Duration {
|
|||
// global minimum from the authority configuration will be used.
|
||||
func (c *Claimer) MinUserSSHCertDuration() time.Duration {
|
||||
if c.claims == nil || c.claims.MinUserSSHDur == nil {
|
||||
if c.claims != nil && c.claims.DefaultUserSSHDur != nil && c.claims.DefaultUserSSHDur.Duration < c.global.MinUserSSHDur.Duration {
|
||||
return c.claims.DefaultUserSSHDur.Duration
|
||||
}
|
||||
return c.global.MinUserSSHDur.Duration
|
||||
}
|
||||
return c.claims.MinUserSSHDur.Duration
|
||||
|
@ -136,6 +145,9 @@ func (c *Claimer) MinUserSSHCertDuration() time.Duration {
|
|||
// global maximum from the authority configuration will be used.
|
||||
func (c *Claimer) MaxUserSSHCertDuration() time.Duration {
|
||||
if c.claims == nil || c.claims.MaxUserSSHDur == nil {
|
||||
if c.claims != nil && c.claims.DefaultUserSSHDur != nil && c.claims.DefaultUserSSHDur.Duration > c.global.MaxUserSSHDur.Duration {
|
||||
return c.claims.DefaultUserSSHDur.Duration
|
||||
}
|
||||
return c.global.MaxUserSSHDur.Duration
|
||||
}
|
||||
return c.claims.MaxUserSSHDur.Duration
|
||||
|
@ -156,6 +168,9 @@ func (c *Claimer) DefaultHostSSHCertDuration() time.Duration {
|
|||
// global minimum from the authority configuration will be used.
|
||||
func (c *Claimer) MinHostSSHCertDuration() time.Duration {
|
||||
if c.claims == nil || c.claims.MinHostSSHDur == nil {
|
||||
if c.claims != nil && c.claims.DefaultHostSSHDur != nil && c.claims.DefaultHostSSHDur.Duration < c.global.MinHostSSHDur.Duration {
|
||||
return c.claims.DefaultHostSSHDur.Duration
|
||||
}
|
||||
return c.global.MinHostSSHDur.Duration
|
||||
}
|
||||
return c.claims.MinHostSSHDur.Duration
|
||||
|
@ -166,6 +181,9 @@ func (c *Claimer) MinHostSSHCertDuration() time.Duration {
|
|||
// global maximum from the authority configuration will be used.
|
||||
func (c *Claimer) MaxHostSSHCertDuration() time.Duration {
|
||||
if c.claims == nil || c.claims.MaxHostSSHDur == nil {
|
||||
if c.claims != nil && c.claims.DefaultHostSSHDur != nil && c.claims.DefaultHostSSHDur.Duration > c.global.MaxHostSSHDur.Duration {
|
||||
return c.claims.DefaultHostSSHDur.Duration
|
||||
}
|
||||
return c.global.MaxHostSSHDur.Duration
|
||||
}
|
||||
return c.claims.MaxHostSSHDur.Duration
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"go.step.sm/crypto/jose"
|
||||
)
|
||||
|
||||
|
@ -45,6 +45,8 @@ type loadByTokenPayload struct {
|
|||
type Collection struct {
|
||||
byID *sync.Map
|
||||
byKey *sync.Map
|
||||
byName *sync.Map
|
||||
byTokenID *sync.Map
|
||||
sorted provisionerSlice
|
||||
audiences Audiences
|
||||
}
|
||||
|
@ -55,6 +57,8 @@ func NewCollection(audiences Audiences) *Collection {
|
|||
return &Collection{
|
||||
byID: new(sync.Map),
|
||||
byKey: new(sync.Map),
|
||||
byName: new(sync.Map),
|
||||
byTokenID: new(sync.Map),
|
||||
audiences: audiences,
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +68,18 @@ func (c *Collection) Load(id string) (Interface, bool) {
|
|||
return loadProvisioner(c.byID, id)
|
||||
}
|
||||
|
||||
// LoadByName a provisioner by name.
|
||||
func (c *Collection) LoadByName(name string) (Interface, bool) {
|
||||
return loadProvisioner(c.byName, name)
|
||||
}
|
||||
|
||||
// LoadByTokenID a provisioner by identifier found in token.
|
||||
// For different provisioner types this identifier may be found in in different
|
||||
// attributes of the token.
|
||||
func (c *Collection) LoadByTokenID(tokenProvisionerID string) (Interface, bool) {
|
||||
return loadProvisioner(c.byTokenID, tokenProvisionerID)
|
||||
}
|
||||
|
||||
// LoadByToken parses the token claims and loads the provisioner associated.
|
||||
func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) (Interface, bool) {
|
||||
var audiences []string
|
||||
|
@ -79,11 +95,12 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
|
|||
if matchesAudience(claims.Audience, audiences) {
|
||||
// Use fragment to get provisioner name (GCP, AWS, SSHPOP)
|
||||
if fragment != "" {
|
||||
return c.Load(fragment)
|
||||
return c.LoadByTokenID(fragment)
|
||||
}
|
||||
// If matches with stored audiences it will be a JWT token (default), and
|
||||
// the id would be <issuer>:<kid>.
|
||||
return c.Load(claims.Issuer + ":" + token.Headers[0].KeyID)
|
||||
// TODO: is this ok?
|
||||
return c.LoadByTokenID(claims.Issuer + ":" + token.Headers[0].KeyID)
|
||||
}
|
||||
|
||||
// The ID will be just the clientID stored in azp, aud or tid.
|
||||
|
@ -94,7 +111,7 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
|
|||
|
||||
// Kubernetes Service Account tokens.
|
||||
if payload.Issuer == k8sSAIssuer {
|
||||
if p, ok := c.Load(K8sSAID); ok {
|
||||
if p, ok := c.LoadByTokenID(K8sSAID); ok {
|
||||
return p, ok
|
||||
}
|
||||
// Kubernetes service account provisioner not found
|
||||
|
@ -108,18 +125,18 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
|
|||
|
||||
// Try with azp (OIDC)
|
||||
if len(payload.AuthorizedParty) > 0 {
|
||||
if p, ok := c.Load(payload.AuthorizedParty); ok {
|
||||
if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok {
|
||||
return p, ok
|
||||
}
|
||||
}
|
||||
// Try with tid (Azure)
|
||||
if payload.TenantID != "" {
|
||||
if p, ok := c.Load(payload.TenantID); ok {
|
||||
if p, ok := c.LoadByTokenID(payload.TenantID); ok {
|
||||
return p, ok
|
||||
}
|
||||
}
|
||||
// Fallback to aud
|
||||
return c.Load(payload.Audience[0])
|
||||
return c.LoadByTokenID(payload.Audience[0])
|
||||
}
|
||||
|
||||
// LoadByCertificate looks for the provisioner extension and extracts the
|
||||
|
@ -131,24 +148,7 @@ func (c *Collection) LoadByCertificate(cert *x509.Certificate) (Interface, bool)
|
|||
if _, err := asn1.Unmarshal(e.Value, &provisioner); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
switch Type(provisioner.Type) {
|
||||
case TypeJWK:
|
||||
return c.Load(string(provisioner.Name) + ":" + string(provisioner.CredentialID))
|
||||
case TypeAWS:
|
||||
return c.Load("aws/" + string(provisioner.Name))
|
||||
case TypeGCP:
|
||||
return c.Load("gcp/" + string(provisioner.Name))
|
||||
case TypeACME:
|
||||
return c.Load("acme/" + string(provisioner.Name))
|
||||
case TypeSCEP:
|
||||
return c.Load("scep/" + string(provisioner.Name))
|
||||
case TypeX5C:
|
||||
return c.Load("x5c/" + string(provisioner.Name))
|
||||
case TypeK8sSA:
|
||||
return c.Load(K8sSAID)
|
||||
default:
|
||||
return c.Load(string(provisioner.CredentialID))
|
||||
}
|
||||
return c.LoadByName(string(provisioner.Name))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,7 +173,21 @@ func (c *Collection) LoadEncryptedKey(keyID string) (string, bool) {
|
|||
func (c *Collection) Store(p Interface) error {
|
||||
// Store provisioner always in byID. ID must be unique.
|
||||
if _, loaded := c.byID.LoadOrStore(p.GetID(), p); loaded {
|
||||
return errors.New("cannot add multiple provisioners with the same id")
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"cannot add multiple provisioners with the same id")
|
||||
}
|
||||
// Store provisioner always by name.
|
||||
if _, loaded := c.byName.LoadOrStore(p.GetName(), p); loaded {
|
||||
c.byID.Delete(p.GetID())
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"cannot add multiple provisioners with the same name")
|
||||
}
|
||||
// Store provisioner always by ID presented in token.
|
||||
if _, loaded := c.byTokenID.LoadOrStore(p.GetIDForToken(), p); loaded {
|
||||
c.byID.Delete(p.GetID())
|
||||
c.byName.Delete(p.GetName())
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"cannot add multiple provisioners with the same token identifier")
|
||||
}
|
||||
|
||||
// Store provisioner in byKey if EncryptedKey is defined.
|
||||
|
@ -197,6 +211,65 @@ func (c *Collection) Store(p Interface) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Remove deletes an provisioner from all associated collections and lists.
|
||||
func (c *Collection) Remove(id string) error {
|
||||
prov, ok := c.Load(id)
|
||||
if !ok {
|
||||
return admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", id)
|
||||
}
|
||||
|
||||
var found bool
|
||||
for i, elem := range c.sorted {
|
||||
if elem.provisioner.GetID() == id {
|
||||
// Remove index in sorted list
|
||||
copy(c.sorted[i:], c.sorted[i+1:]) // Shift a[i+1:] left one index.
|
||||
c.sorted[len(c.sorted)-1] = uidProvisioner{} // Erase last element (write zero value).
|
||||
c.sorted = c.sorted[:len(c.sorted)-1] // Truncate slice.
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found in sorted list", prov.GetName())
|
||||
}
|
||||
|
||||
c.byID.Delete(id)
|
||||
c.byName.Delete(prov.GetName())
|
||||
c.byTokenID.Delete(prov.GetIDForToken())
|
||||
if kid, _, ok := prov.GetEncryptedKey(); ok {
|
||||
c.byKey.Delete(kid)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates the given provisioner in all related lists and collections.
|
||||
func (c *Collection) Update(nu Interface) error {
|
||||
old, ok := c.Load(nu.GetID())
|
||||
if !ok {
|
||||
return admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", nu.GetID())
|
||||
}
|
||||
|
||||
if old.GetName() != nu.GetName() {
|
||||
if _, ok := c.LoadByName(nu.GetName()); ok {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"provisioner with name %s already exists", nu.GetName())
|
||||
}
|
||||
}
|
||||
if old.GetIDForToken() != nu.GetIDForToken() {
|
||||
if _, ok := c.LoadByTokenID(nu.GetIDForToken()); ok {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"provisioner with Token ID %s already exists", nu.GetIDForToken())
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.Remove(old.GetID()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Store(nu)
|
||||
}
|
||||
|
||||
// Find implements pagination on a list of sorted provisioners.
|
||||
func (c *Collection) Find(cursor string, limit int) (List, string) {
|
||||
switch {
|
||||
|
|
|
@ -132,6 +132,7 @@ func TestCollection_LoadByToken(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Collection{
|
||||
byID: tt.fields.byID,
|
||||
byTokenID: tt.fields.byID,
|
||||
audiences: tt.fields.audiences,
|
||||
}
|
||||
got, got1 := c.LoadByToken(tt.args.token, tt.args.claims)
|
||||
|
@ -153,10 +154,10 @@ func TestCollection_LoadByCertificate(t *testing.T) {
|
|||
p3, err := generateACME()
|
||||
assert.FatalError(t, err)
|
||||
|
||||
byID := new(sync.Map)
|
||||
byID.Store(p1.GetID(), p1)
|
||||
byID.Store(p2.GetID(), p2)
|
||||
byID.Store(p3.GetID(), p3)
|
||||
byName := new(sync.Map)
|
||||
byName.Store(p1.GetName(), p1)
|
||||
byName.Store(p2.GetName(), p2)
|
||||
byName.Store(p3.GetName(), p3)
|
||||
|
||||
ok1Ext, err := createProvisionerExtension(1, p1.Name, p1.Key.KeyID)
|
||||
assert.FatalError(t, err)
|
||||
|
@ -186,7 +187,7 @@ func TestCollection_LoadByCertificate(t *testing.T) {
|
|||
}
|
||||
|
||||
type fields struct {
|
||||
byID *sync.Map
|
||||
byName *sync.Map
|
||||
audiences Audiences
|
||||
}
|
||||
type args struct {
|
||||
|
@ -199,17 +200,17 @@ func TestCollection_LoadByCertificate(t *testing.T) {
|
|||
want Interface
|
||||
want1 bool
|
||||
}{
|
||||
{"ok1", fields{byID, testAudiences}, args{ok1Cert}, p1, true},
|
||||
{"ok2", fields{byID, testAudiences}, args{ok2Cert}, p2, true},
|
||||
{"ok3", fields{byID, testAudiences}, args{ok3Cert}, p3, true},
|
||||
{"noExtension", fields{byID, testAudiences}, args{&x509.Certificate{}}, &noop{}, true},
|
||||
{"notFound", fields{byID, testAudiences}, args{notFoundCert}, nil, false},
|
||||
{"badCert", fields{byID, testAudiences}, args{badCert}, nil, false},
|
||||
{"ok1", fields{byName, testAudiences}, args{ok1Cert}, p1, true},
|
||||
{"ok2", fields{byName, testAudiences}, args{ok2Cert}, p2, true},
|
||||
{"ok3", fields{byName, testAudiences}, args{ok3Cert}, p3, true},
|
||||
{"noExtension", fields{byName, testAudiences}, args{&x509.Certificate{}}, &noop{}, true},
|
||||
{"notFound", fields{byName, testAudiences}, args{notFoundCert}, nil, false},
|
||||
{"badCert", fields{byName, testAudiences}, args{badCert}, nil, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Collection{
|
||||
byID: tt.fields.byID,
|
||||
byName: tt.fields.byName,
|
||||
audiences: tt.fields.audiences,
|
||||
}
|
||||
got, got1 := c.LoadByCertificate(tt.args.cert)
|
||||
|
|
|
@ -78,6 +78,7 @@ func newGCPConfig() *gcpConfig {
|
|||
// https://cloud.google.com/compute/docs/instances/verifying-instance-identity
|
||||
type GCP struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
ServiceAccounts []string `json:"serviceAccounts"`
|
||||
|
@ -96,6 +97,16 @@ type GCP struct {
|
|||
// GetID returns the provisioner unique identifier. The name should uniquely
|
||||
// identify any GCP provisioner.
|
||||
func (p *GCP) GetID() string {
|
||||
if p.ID != "" {
|
||||
return p.ID
|
||||
}
|
||||
return p.GetIDForToken()
|
||||
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (p *GCP) GetIDForToken() string {
|
||||
return "gcp/" + p.Name
|
||||
}
|
||||
|
||||
|
@ -123,7 +134,7 @@ func (p *GCP) GetTokenID(token string) (string, error) {
|
|||
// Create unique ID for Trust On First Use (TOFU). Only the first instance
|
||||
// per provisioner is allowed as we don't have a way to trust the given
|
||||
// sans.
|
||||
unique := fmt.Sprintf("%s.%s", p.GetID(), claims.Google.ComputeEngine.InstanceID)
|
||||
unique := fmt.Sprintf("%s.%s", p.GetIDForToken(), claims.Google.ComputeEngine.InstanceID)
|
||||
sum := sha256.Sum256([]byte(unique))
|
||||
return strings.ToLower(hex.EncodeToString(sum[:])), nil
|
||||
}
|
||||
|
@ -157,7 +168,7 @@ func (p *GCP) GetIdentityURL(audience string) string {
|
|||
|
||||
// GetIdentityToken does an HTTP request to the identity url.
|
||||
func (p *GCP) GetIdentityToken(subject, caURL string) (string, error) {
|
||||
audience, err := generateSignAudience(caURL, p.GetID())
|
||||
audience, err := generateSignAudience(caURL, p.GetIDForToken())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -205,7 +216,7 @@ func (p *GCP) Init(config Config) error {
|
|||
return err
|
||||
}
|
||||
|
||||
p.audiences = config.Audiences.WithFragment(p.GetID())
|
||||
p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -266,7 +277,7 @@ func (p *GCP) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
// AuthorizeRenew returns an error if the renewal is disabled.
|
||||
func (p *GCP) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||
if p.claimer.IsDisableRenewal() {
|
||||
return errs.Unauthorized("gcp.AuthorizeRenew; renew is disabled for gcp provisioner %s", p.GetID())
|
||||
return errs.Unauthorized("gcp.AuthorizeRenew; renew is disabled for gcp provisioner '%s'", p.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -371,7 +382,7 @@ func (p *GCP) authorizeToken(token string) (*gcpPayload, error) {
|
|||
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||
func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errs.Unauthorized("gcp.AuthorizeSSHSign; sshCA is disabled for gcp provisioner %s", p.GetID())
|
||||
return nil, errs.Unauthorized("gcp.AuthorizeSSHSign; sshCA is disabled for gcp provisioner '%s'", p.GetName())
|
||||
}
|
||||
claims, err := p.authorizeToken(token)
|
||||
if err != nil {
|
||||
|
|
|
@ -28,6 +28,7 @@ type stepPayload struct {
|
|||
// signature requests.
|
||||
type JWK struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Key *jose.JSONWebKey `json:"key"`
|
||||
|
@ -41,6 +42,15 @@ type JWK struct {
|
|||
// GetID returns the provisioner unique identifier. The name and credential id
|
||||
// should uniquely identify any JWK provisioner.
|
||||
func (p *JWK) GetID() string {
|
||||
if p.ID != "" {
|
||||
return p.ID
|
||||
}
|
||||
return p.GetIDForToken()
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (p *JWK) GetIDForToken() string {
|
||||
return p.Name + ":" + p.Key.KeyID
|
||||
}
|
||||
|
||||
|
@ -184,7 +194,7 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
// certificate was configured to allow renewals.
|
||||
func (p *JWK) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||
if p.claimer.IsDisableRenewal() {
|
||||
return errs.Unauthorized("jwk.AuthorizeRenew; renew is disabled for jwk provisioner %s", p.GetID())
|
||||
return errs.Unauthorized("jwk.AuthorizeRenew; renew is disabled for jwk provisioner '%s'", p.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -192,7 +202,7 @@ func (p *JWK) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error
|
|||
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||
func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errs.Unauthorized("jwk.AuthorizeSSHSign; sshCA is disabled for jwk provisioner %s", p.GetID())
|
||||
return nil, errs.Unauthorized("jwk.AuthorizeSSHSign; sshCA is disabled for jwk provisioner '%s'", p.GetName())
|
||||
}
|
||||
claims, err := p.authorizeToken(token, p.audiences.SSHSign)
|
||||
if err != nil {
|
||||
|
|
|
@ -77,7 +77,7 @@ func TestJWK_Init(t *testing.T) {
|
|||
"fail-bad-claims": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &JWK{Name: "foo", Type: "bar", Key: &jose.JSONWebKey{}, audiences: testAudiences, Claims: &Claims{DefaultTLSDur: &Duration{0}}},
|
||||
err: errors.New("claims: DefaultTLSCertDuration must be greater than 0"),
|
||||
err: errors.New("claims: MinTLSCertDuration must be greater than 0"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) ProvisionerValidateTest {
|
||||
|
|
|
@ -42,6 +42,7 @@ type k8sSAPayload struct {
|
|||
// entity trusted to make signature requests.
|
||||
type K8sSA struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
PubKeys []byte `json:"publicKeys,omitempty"`
|
||||
|
@ -56,6 +57,15 @@ type K8sSA struct {
|
|||
// GetID returns the provisioner unique identifier. The name and credential id
|
||||
// should uniquely identify any K8sSA provisioner.
|
||||
func (p *K8sSA) GetID() string {
|
||||
if p.ID != "" {
|
||||
return p.ID
|
||||
}
|
||||
return p.GetIDForToken()
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (p *K8sSA) GetIDForToken() string {
|
||||
return K8sSAID
|
||||
}
|
||||
|
||||
|
@ -101,12 +111,12 @@ func (p *K8sSA) Init(config Config) (err error) {
|
|||
}
|
||||
key, err := pemutil.ParseKey(pem.EncodeToMemory(block))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing public key in provisioner %s", p.GetID())
|
||||
return errors.Wrapf(err, "error parsing public key in provisioner '%s'", p.GetName())
|
||||
}
|
||||
switch q := key.(type) {
|
||||
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
|
||||
default:
|
||||
return errors.Errorf("Unexpected public key type %T in provisioner %s", q, p.GetID())
|
||||
return errors.Errorf("Unexpected public key type %T in provisioner '%s'", q, p.GetName())
|
||||
}
|
||||
p.pubKeys = append(p.pubKeys, key)
|
||||
}
|
||||
|
@ -240,7 +250,7 @@ func (p *K8sSA) AuthorizeSign(ctx context.Context, token string) ([]SignOption,
|
|||
// AuthorizeRenew returns an error if the renewal is disabled.
|
||||
func (p *K8sSA) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||
if p.claimer.IsDisableRenewal() {
|
||||
return errs.Unauthorized("k8ssa.AuthorizeRenew; renew is disabled for k8sSA provisioner %s", p.GetID())
|
||||
return errs.Unauthorized("k8ssa.AuthorizeRenew; renew is disabled for k8sSA provisioner '%s'", p.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -248,7 +258,7 @@ func (p *K8sSA) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) erro
|
|||
// AuthorizeSSHSign validates an request for an SSH certificate.
|
||||
func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner %s", p.GetID())
|
||||
return nil, errs.Unauthorized("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner '%s'", p.GetName())
|
||||
}
|
||||
claims, err := p.authorizeToken(token, p.audiences.SSHSign)
|
||||
if err != nil {
|
||||
|
|
|
@ -198,7 +198,7 @@ func TestK8sSA_AuthorizeRenew(t *testing.T) {
|
|||
p: p,
|
||||
cert: &x509.Certificate{},
|
||||
code: http.StatusUnauthorized,
|
||||
err: errors.Errorf("k8ssa.AuthorizeRenew; renew is disabled for k8sSA provisioner %s", p.GetID()),
|
||||
err: errors.Errorf("k8ssa.AuthorizeRenew; renew is disabled for k8sSA provisioner '%s'", p.GetName()),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
|
@ -319,7 +319,7 @@ func TestK8sSA_AuthorizeSSHSign(t *testing.T) {
|
|||
p: p,
|
||||
token: "foo",
|
||||
code: http.StatusUnauthorized,
|
||||
err: errors.Errorf("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner %s", p.GetID()),
|
||||
err: errors.Errorf("k8ssa.AuthorizeSSHSign; sshCA is disabled for k8sSA provisioner '%s'", p.GetName()),
|
||||
}
|
||||
},
|
||||
"fail/invalid-token": func(t *testing.T) test {
|
||||
|
|
|
@ -14,6 +14,10 @@ func (p *noop) GetID() string {
|
|||
return "noop"
|
||||
}
|
||||
|
||||
func (p *noop) GetIDForToken() string {
|
||||
return "noop"
|
||||
}
|
||||
|
||||
func (p *noop) GetTokenID(token string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ type openIDPayload struct {
|
|||
// ClientSecret is mandatory, but it can be an empty string.
|
||||
type OIDC struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"clientID"`
|
||||
|
@ -111,6 +112,15 @@ func sanitizeEmail(email string) string {
|
|||
// GetID returns the provisioner unique identifier, the OIDC provisioner the
|
||||
// uses the clientID for this.
|
||||
func (o *OIDC) GetID() string {
|
||||
if o.ID != "" {
|
||||
return o.ID
|
||||
}
|
||||
return o.GetIDForToken()
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (o *OIDC) GetIDForToken() string {
|
||||
return o.ClientID
|
||||
}
|
||||
|
||||
|
@ -367,7 +377,7 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e
|
|||
// certificate was configured to allow renewals.
|
||||
func (o *OIDC) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||
if o.claimer.IsDisableRenewal() {
|
||||
return errs.Unauthorized("oidc.AuthorizeRenew; renew is disabled for oidc provisioner %s", o.GetID())
|
||||
return errs.Unauthorized("oidc.AuthorizeRenew; renew is disabled for oidc provisioner '%s'", o.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -375,7 +385,7 @@ func (o *OIDC) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error
|
|||
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||
func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||
if !o.claimer.IsSSHCAEnabled() {
|
||||
return nil, errs.Unauthorized("oidc.AuthorizeSSHSign; sshCA is disabled for oidc provisioner %s", o.GetID())
|
||||
return nil, errs.Unauthorized("oidc.AuthorizeSSHSign; sshCA is disabled for oidc provisioner '%s'", o.GetName())
|
||||
}
|
||||
claims, err := o.authorizeToken(token)
|
||||
if err != nil {
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
// Interface is the interface that all provisioner types must implement.
|
||||
type Interface interface {
|
||||
GetID() string
|
||||
GetIDForToken() string
|
||||
GetTokenID(token string) (string, error)
|
||||
GetName() string
|
||||
GetType() Type
|
||||
|
@ -394,6 +395,7 @@ type MockProvisioner struct {
|
|||
Mret1, Mret2, Mret3 interface{}
|
||||
Merr error
|
||||
MgetID func() string
|
||||
MgetIDForToken func() string
|
||||
MgetTokenID func(string) (string, error)
|
||||
MgetName func() string
|
||||
MgetType func() Type
|
||||
|
@ -416,6 +418,14 @@ func (m *MockProvisioner) GetID() string {
|
|||
return m.Mret1.(string)
|
||||
}
|
||||
|
||||
// GetIDForToken mock
|
||||
func (m *MockProvisioner) GetIDForToken() string {
|
||||
if m.MgetIDForToken != nil {
|
||||
return m.MgetIDForToken()
|
||||
}
|
||||
return m.Mret1.(string)
|
||||
}
|
||||
|
||||
// GetTokenID mock
|
||||
func (m *MockProvisioner) GetTokenID(token string) (string, error) {
|
||||
if m.MgetTokenID != nil {
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
// SCEP provisioning flow
|
||||
type SCEP struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
|
||||
|
@ -27,7 +28,16 @@ type SCEP struct {
|
|||
}
|
||||
|
||||
// GetID returns the provisioner unique identifier.
|
||||
func (s SCEP) GetID() string {
|
||||
func (s *SCEP) GetID() string {
|
||||
if s.ID != "" {
|
||||
return s.ID
|
||||
}
|
||||
return s.GetIDForToken()
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (s *SCEP) GetIDForToken() string {
|
||||
return "scep/" + s.Name
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ type sshPOPPayload struct {
|
|||
// signature requests.
|
||||
type SSHPOP struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Claims *Claims `json:"claims,omitempty"`
|
||||
|
@ -38,6 +39,15 @@ type SSHPOP struct {
|
|||
// GetID returns the provisioner unique identifier. The name and credential id
|
||||
// should uniquely identify any SSH-POP provisioner.
|
||||
func (p *SSHPOP) GetID() string {
|
||||
if p.ID != "" {
|
||||
return p.ID
|
||||
}
|
||||
return p.GetIDForToken()
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (p *SSHPOP) GetIDForToken() string {
|
||||
return "sshpop/" + p.Name
|
||||
}
|
||||
|
||||
|
@ -91,7 +101,7 @@ func (p *SSHPOP) Init(config Config) error {
|
|||
return err
|
||||
}
|
||||
|
||||
p.audiences = config.Audiences.WithFragment(p.GetID())
|
||||
p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
|
||||
p.db = config.DB
|
||||
p.sshPubKeys = config.SSHKeys
|
||||
return nil
|
||||
|
|
|
@ -26,6 +26,7 @@ type x5cPayload struct {
|
|||
// signature requests.
|
||||
type X5C struct {
|
||||
*base
|
||||
ID string `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Roots []byte `json:"roots"`
|
||||
|
@ -39,6 +40,15 @@ type X5C struct {
|
|||
// GetID returns the provisioner unique identifier. The name and credential id
|
||||
// should uniquely identify any X5C provisioner.
|
||||
func (p *X5C) GetID() string {
|
||||
if p.ID != "" {
|
||||
return p.ID
|
||||
}
|
||||
return p.GetIDForToken()
|
||||
}
|
||||
|
||||
// GetIDForToken returns an identifier that will be used to load the provisioner
|
||||
// from a token.
|
||||
func (p *X5C) GetIDForToken() string {
|
||||
return "x5c/" + p.Name
|
||||
}
|
||||
|
||||
|
@ -106,7 +116,7 @@ func (p *X5C) Init(config Config) error {
|
|||
|
||||
// Verify that at least one root was found.
|
||||
if len(p.rootPool.Subjects()) == 0 {
|
||||
return errors.Errorf("no x509 certificates found in roots attribute for provisioner %s", p.GetName())
|
||||
return errors.Errorf("no x509 certificates found in roots attribute for provisioner '%s'", p.GetName())
|
||||
}
|
||||
|
||||
// Update claims with global ones
|
||||
|
@ -115,7 +125,7 @@ func (p *X5C) Init(config Config) error {
|
|||
return err
|
||||
}
|
||||
|
||||
p.audiences = config.Audiences.WithFragment(p.GetID())
|
||||
p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -130,6 +140,7 @@ func (p *X5C) authorizeToken(token string, audiences []string) (*x5cPayload, err
|
|||
|
||||
verifiedChains, err := jwt.Headers[0].Certificates(x509.VerifyOptions{
|
||||
Roots: p.rootPool,
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusUnauthorized, err,
|
||||
|
@ -224,7 +235,7 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er
|
|||
// AuthorizeRenew returns an error if the renewal is disabled.
|
||||
func (p *X5C) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
|
||||
if p.claimer.IsDisableRenewal() {
|
||||
return errs.Unauthorized("x5c.AuthorizeRenew; renew is disabled for x5c provisioner %s", p.GetID())
|
||||
return errs.Unauthorized("x5c.AuthorizeRenew; renew is disabled for x5c provisioner '%s'", p.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -232,7 +243,7 @@ func (p *X5C) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error
|
|||
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
|
||||
func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
|
||||
if !p.claimer.IsSSHCAEnabled() {
|
||||
return nil, errs.Unauthorized("x5c.AuthorizeSSHSign; sshCA is disabled for x5c provisioner %s", p.GetID())
|
||||
return nil, errs.Unauthorized("x5c.AuthorizeSSHSign; sshCA is disabled for x5c provisioner '%s'", p.GetName())
|
||||
}
|
||||
|
||||
claims, err := p.authorizeToken(token, p.audiences.SSHSign)
|
||||
|
|
|
@ -70,7 +70,7 @@ func TestX5C_Init(t *testing.T) {
|
|||
"fail/no-valid-root-certs": func(t *testing.T) ProvisionerValidateTest {
|
||||
return ProvisionerValidateTest{
|
||||
p: &X5C{Name: "foo", Type: "bar", Roots: []byte("foo"), audiences: testAudiences},
|
||||
err: errors.Errorf("no x509 certificates found in roots attribute for provisioner foo"),
|
||||
err: errors.Errorf("no x509 certificates found in roots attribute for provisioner 'foo'"),
|
||||
}
|
||||
},
|
||||
"fail/invalid-duration": func(t *testing.T) ProvisionerValidateTest {
|
||||
|
@ -79,7 +79,7 @@ func TestX5C_Init(t *testing.T) {
|
|||
p.Claims = &Claims{DefaultTLSDur: &Duration{0}}
|
||||
return ProvisionerValidateTest{
|
||||
p: p,
|
||||
err: errors.New("claims: DefaultTLSCertDuration must be greater than 0"),
|
||||
err: errors.New("claims: MinTLSCertDuration must be greater than 0"),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) ProvisionerValidateTest {
|
||||
|
@ -568,7 +568,7 @@ func TestX5C_AuthorizeRenew(t *testing.T) {
|
|||
return test{
|
||||
p: p,
|
||||
code: http.StatusUnauthorized,
|
||||
err: errors.Errorf("x5c.AuthorizeRenew; renew is disabled for x5c provisioner %s", p.GetID()),
|
||||
err: errors.Errorf("x5c.AuthorizeRenew; renew is disabled for x5c provisioner '%s'", p.GetName()),
|
||||
}
|
||||
},
|
||||
"ok": func(t *testing.T) test {
|
||||
|
@ -624,7 +624,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
|
|||
p: p,
|
||||
token: "foo",
|
||||
code: http.StatusUnauthorized,
|
||||
err: errors.Errorf("x5c.AuthorizeSSHSign; sshCA is disabled for x5c provisioner %s", p.GetID()),
|
||||
err: errors.Errorf("x5c.AuthorizeSSHSign; sshCA is disabled for x5c provisioner '%s'", p.GetName()),
|
||||
}
|
||||
},
|
||||
"fail/invalid-token": func(t *testing.T) test {
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
package authority
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/linkedca"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
// GetEncryptedKey returns the JWE key corresponding to the given kid argument.
|
||||
func (a *Authority) GetEncryptedKey(kid string) (string, error) {
|
||||
a.adminMutex.RLock()
|
||||
defer a.adminMutex.RUnlock()
|
||||
key, ok := a.provisioners.LoadEncryptedKey(kid)
|
||||
if !ok {
|
||||
return "", errs.NotFound("encrypted key with kid %s was not found", kid)
|
||||
|
@ -19,6 +29,8 @@ func (a *Authority) GetEncryptedKey(kid string) (string, error) {
|
|||
// GetProvisioners returns a map listing each provisioner and the JWK Key Set
|
||||
// with their public keys.
|
||||
func (a *Authority) GetProvisioners(cursor string, limit int) (provisioner.List, string, error) {
|
||||
a.adminMutex.RLock()
|
||||
defer a.adminMutex.RUnlock()
|
||||
provisioners, nextCursor := a.provisioners.Find(cursor, limit)
|
||||
return provisioners, nextCursor, nil
|
||||
}
|
||||
|
@ -26,18 +38,564 @@ func (a *Authority) GetProvisioners(cursor string, limit int) (provisioner.List,
|
|||
// LoadProvisionerByCertificate returns an interface to the provisioner that
|
||||
// provisioned the certificate.
|
||||
func (a *Authority) LoadProvisionerByCertificate(crt *x509.Certificate) (provisioner.Interface, error) {
|
||||
a.adminMutex.RLock()
|
||||
defer a.adminMutex.RUnlock()
|
||||
p, ok := a.provisioners.LoadByCertificate(crt)
|
||||
if !ok {
|
||||
return nil, errs.NotFound("provisioner not found")
|
||||
return nil, admin.NewError(admin.ErrorNotFoundType, "unable to load provisioner from certificate")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// LoadProvisionerByToken returns an interface to the provisioner that
|
||||
// provisioned the token.
|
||||
func (a *Authority) LoadProvisionerByToken(token *jwt.JSONWebToken, claims *jwt.Claims) (provisioner.Interface, error) {
|
||||
a.adminMutex.RLock()
|
||||
defer a.adminMutex.RUnlock()
|
||||
p, ok := a.provisioners.LoadByToken(token, claims)
|
||||
if !ok {
|
||||
return nil, admin.NewError(admin.ErrorNotFoundType, "unable to load provisioner from token")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// LoadProvisionerByID returns an interface to the provisioner with the given ID.
|
||||
func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) {
|
||||
a.adminMutex.RLock()
|
||||
defer a.adminMutex.RUnlock()
|
||||
p, ok := a.provisioners.Load(id)
|
||||
if !ok {
|
||||
return nil, errs.NotFound("provisioner not found")
|
||||
return nil, admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", id)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// LoadProvisionerByName returns an interface to the provisioner with the given Name.
|
||||
func (a *Authority) LoadProvisionerByName(name string) (provisioner.Interface, error) {
|
||||
a.adminMutex.RLock()
|
||||
defer a.adminMutex.RUnlock()
|
||||
p, ok := a.provisioners.LoadByName(name)
|
||||
if !ok {
|
||||
return nil, admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", name)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (a *Authority) generateProvisionerConfig(ctx context.Context) (*provisioner.Config, error) {
|
||||
// Merge global and configuration claims
|
||||
claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, config.GlobalProvisionerClaims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: should we also be combining the ssh federated roots here?
|
||||
// If we rotate ssh roots keys, sshpop provisioner will lose ability to
|
||||
// validate old SSH certificates, unless they are added as federated certs.
|
||||
sshKeys, err := a.GetSSHRoots(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &provisioner.Config{
|
||||
Claims: claimer.Claims(),
|
||||
Audiences: a.config.GetAudiences(),
|
||||
DB: a.db,
|
||||
SSHKeys: &provisioner.SSHKeys{
|
||||
UserKeys: sshKeys.UserKeys,
|
||||
HostKeys: sshKeys.HostKeys,
|
||||
},
|
||||
GetIdentityFunc: a.getIdentityFunc,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// StoreProvisioner stores an provisioner.Interface to the authority.
|
||||
func (a *Authority) StoreProvisioner(ctx context.Context, prov *linkedca.Provisioner) error {
|
||||
a.adminMutex.Lock()
|
||||
defer a.adminMutex.Unlock()
|
||||
|
||||
certProv, err := ProvisionerToCertificates(prov)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err,
|
||||
"error converting to certificates provisioner from linkedca provisioner")
|
||||
}
|
||||
|
||||
if _, ok := a.provisioners.LoadByName(prov.GetName()); ok {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"provisioner with name %s already exists", prov.GetName())
|
||||
}
|
||||
if _, ok := a.provisioners.LoadByTokenID(certProv.GetIDForToken()); ok {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"provisioner with token ID %s already exists", certProv.GetIDForToken())
|
||||
}
|
||||
|
||||
// Store to database -- this will set the ID.
|
||||
if err := a.adminDB.CreateProvisioner(ctx, prov); err != nil {
|
||||
return admin.WrapErrorISE(err, "error creating admin")
|
||||
}
|
||||
|
||||
// We need a new conversion that has the newly set ID.
|
||||
certProv, err = ProvisionerToCertificates(prov)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err,
|
||||
"error converting to certificates provisioner from linkedca provisioner")
|
||||
}
|
||||
|
||||
provisionerConfig, err := a.generateProvisionerConfig(ctx)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error generating provisioner config")
|
||||
}
|
||||
|
||||
if err := certProv.Init(*provisionerConfig); err != nil {
|
||||
return admin.WrapErrorISE(err, "error initializing provisioner %s", prov.Name)
|
||||
}
|
||||
|
||||
if err := a.provisioners.Store(certProv); err != nil {
|
||||
if err := a.reloadAdminResources(ctx); err != nil {
|
||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner store")
|
||||
}
|
||||
return admin.WrapErrorISE(err, "error storing provisioner in authority cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProvisioner stores an provisioner.Interface to the authority.
|
||||
func (a *Authority) UpdateProvisioner(ctx context.Context, nu *linkedca.Provisioner) error {
|
||||
a.adminMutex.Lock()
|
||||
defer a.adminMutex.Unlock()
|
||||
|
||||
certProv, err := ProvisionerToCertificates(nu)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err,
|
||||
"error converting to certificates provisioner from linkedca provisioner")
|
||||
}
|
||||
|
||||
provisionerConfig, err := a.generateProvisionerConfig(ctx)
|
||||
if err != nil {
|
||||
return admin.WrapErrorISE(err, "error generating provisioner config")
|
||||
}
|
||||
|
||||
if err := certProv.Init(*provisionerConfig); err != nil {
|
||||
return admin.WrapErrorISE(err, "error initializing provisioner %s", nu.Name)
|
||||
}
|
||||
|
||||
if err := a.provisioners.Update(certProv); err != nil {
|
||||
return admin.WrapErrorISE(err, "error updating provisioner '%s' in authority cache", nu.Name)
|
||||
}
|
||||
if err := a.adminDB.UpdateProvisioner(ctx, nu); err != nil {
|
||||
if err := a.reloadAdminResources(ctx); err != nil {
|
||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner update")
|
||||
}
|
||||
return admin.WrapErrorISE(err, "error updating provisioner '%s'", nu.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveProvisioner removes an provisioner.Interface from the authority.
|
||||
func (a *Authority) RemoveProvisioner(ctx context.Context, id string) error {
|
||||
a.adminMutex.Lock()
|
||||
defer a.adminMutex.Unlock()
|
||||
|
||||
p, ok := a.provisioners.Load(id)
|
||||
if !ok {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"provisioner %s not found", id)
|
||||
}
|
||||
|
||||
provName, provID := p.GetName(), p.GetID()
|
||||
// Validate
|
||||
// - Check that there will be SUPER_ADMINs that remain after we
|
||||
// remove this provisioner.
|
||||
if a.admins.SuperCount() == a.admins.SuperCountByProvisioner(provName) {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"cannot remove provisioner %s because no super admins will remain", provName)
|
||||
}
|
||||
|
||||
// Delete all admins associated with the provisioner.
|
||||
admins, ok := a.admins.LoadByProvisioner(provName)
|
||||
if ok {
|
||||
for _, adm := range admins {
|
||||
if err := a.removeAdmin(ctx, adm.Id); err != nil {
|
||||
return admin.WrapErrorISE(err, "error deleting admin %s, as part of provisioner %s deletion", adm.Subject, provName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove provisioner from authority caches.
|
||||
if err := a.provisioners.Remove(provID); err != nil {
|
||||
return admin.WrapErrorISE(err, "error removing admin from authority cache")
|
||||
}
|
||||
// Remove provisioner from database.
|
||||
if err := a.adminDB.DeleteProvisioner(ctx, provID); err != nil {
|
||||
if err := a.reloadAdminResources(ctx); err != nil {
|
||||
return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner remove")
|
||||
}
|
||||
return admin.WrapErrorISE(err, "error deleting provisioner %s", provName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateFirstProvisioner(ctx context.Context, db admin.DB, password string) (*linkedca.Provisioner, error) {
|
||||
jwk, jwe, err := jose.GenerateDefaultKeyPair([]byte(password))
|
||||
if err != nil {
|
||||
return nil, admin.WrapErrorISE(err, "error generating JWK key pair")
|
||||
}
|
||||
|
||||
jwkPubBytes, err := jwk.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, admin.WrapErrorISE(err, "error marshaling JWK")
|
||||
}
|
||||
jwePrivStr, err := jwe.CompactSerialize()
|
||||
if err != nil {
|
||||
return nil, admin.WrapErrorISE(err, "error serializing JWE")
|
||||
}
|
||||
|
||||
p := &linkedca.Provisioner{
|
||||
Name: "Admin JWK",
|
||||
Type: linkedca.Provisioner_JWK,
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_JWK{
|
||||
JWK: &linkedca.JWKProvisioner{
|
||||
PublicKey: jwkPubBytes,
|
||||
EncryptedPrivateKey: []byte(jwePrivStr),
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: &linkedca.Claims{
|
||||
X509: &linkedca.X509Claims{
|
||||
Enabled: true,
|
||||
Durations: &linkedca.Durations{
|
||||
Default: "5m",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := db.CreateProvisioner(ctx, p); err != nil {
|
||||
return nil, admin.WrapErrorISE(err, "error creating provisioner")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func ValidateClaims(c *linkedca.Claims) error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
if c.X509 != nil {
|
||||
if c.X509.Durations != nil {
|
||||
if err := ValidateDurations(c.X509.Durations); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.Ssh != nil {
|
||||
if c.Ssh.UserDurations != nil {
|
||||
if err := ValidateDurations(c.Ssh.UserDurations); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.Ssh.HostDurations != nil {
|
||||
if err := ValidateDurations(c.Ssh.HostDurations); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateDurations(d *linkedca.Durations) error {
|
||||
var (
|
||||
err error
|
||||
min, max, def *provisioner.Duration
|
||||
)
|
||||
|
||||
if d.Min != "" {
|
||||
min, err = provisioner.NewDuration(d.Min)
|
||||
if err != nil {
|
||||
return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' is invalid", d.Min)
|
||||
}
|
||||
if min.Value() < 0 {
|
||||
return admin.WrapError(admin.ErrorBadRequestType, err, "min duration '%s' cannot be less than 0", d.Min)
|
||||
}
|
||||
}
|
||||
if d.Max != "" {
|
||||
max, err = provisioner.NewDuration(d.Max)
|
||||
if err != nil {
|
||||
return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' is invalid", d.Max)
|
||||
}
|
||||
if max.Value() < 0 {
|
||||
return admin.WrapError(admin.ErrorBadRequestType, err, "max duration '%s' cannot be less than 0", d.Max)
|
||||
}
|
||||
}
|
||||
if d.Default != "" {
|
||||
def, err = provisioner.NewDuration(d.Default)
|
||||
if err != nil {
|
||||
return admin.WrapError(admin.ErrorBadRequestType, err, "default duration '%s' is invalid", d.Default)
|
||||
}
|
||||
if def.Value() < 0 {
|
||||
return admin.WrapError(admin.ErrorBadRequestType, err, "default duration '%s' cannot be less than 0", d.Default)
|
||||
}
|
||||
}
|
||||
if d.Min != "" && d.Max != "" && min.Value() > max.Value() {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"min duration '%s' cannot be greater than max duration '%s'", d.Min, d.Max)
|
||||
}
|
||||
if d.Min != "" && d.Default != "" && min.Value() > def.Value() {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"min duration '%s' cannot be greater than default duration '%s'", d.Min, d.Default)
|
||||
}
|
||||
if d.Default != "" && d.Max != "" && min.Value() > def.Value() {
|
||||
return admin.NewError(admin.ErrorBadRequestType,
|
||||
"default duration '%s' cannot be greater than max duration '%s'", d.Default, d.Max)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func provisionerListToCertificates(l []*linkedca.Provisioner) (provisioner.List, error) {
|
||||
var nu provisioner.List
|
||||
for _, p := range l {
|
||||
certProv, err := ProvisionerToCertificates(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nu = append(nu, certProv)
|
||||
}
|
||||
return nu, nil
|
||||
}
|
||||
|
||||
func optionsToCertificates(p *linkedca.Provisioner) *provisioner.Options {
|
||||
ops := &provisioner.Options{
|
||||
X509: &provisioner.X509Options{},
|
||||
SSH: &provisioner.SSHOptions{},
|
||||
}
|
||||
if p.X509Template != nil {
|
||||
ops.X509.Template = string(p.X509Template.Template)
|
||||
ops.X509.TemplateData = p.X509Template.Data
|
||||
}
|
||||
if p.SshTemplate != nil {
|
||||
ops.SSH.Template = string(p.SshTemplate.Template)
|
||||
ops.SSH.TemplateData = p.SshTemplate.Data
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.Duration, err error) {
|
||||
if len(d.Min) > 0 {
|
||||
min, err = provisioner.NewDuration(d.Min)
|
||||
if err != nil {
|
||||
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing minimum duration '%s'", d.Min)
|
||||
}
|
||||
}
|
||||
if len(d.Max) > 0 {
|
||||
max, err = provisioner.NewDuration(d.Max)
|
||||
if err != nil {
|
||||
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing maximum duration '%s'", d.Max)
|
||||
}
|
||||
}
|
||||
if len(d.Default) > 0 {
|
||||
def, err = provisioner.NewDuration(d.Default)
|
||||
if err != nil {
|
||||
return nil, nil, nil, admin.WrapErrorISE(err, "error parsing default duration '%s'", d.Default)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// claimsToCertificates converts the linkedca provisioner claims type to the
|
||||
// certifictes claims type.
|
||||
func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) {
|
||||
if c == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pc := &provisioner.Claims{
|
||||
DisableRenewal: &c.DisableRenewal,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if xc := c.X509; xc != nil {
|
||||
if d := xc.Durations; d != nil {
|
||||
pc.MinTLSDur, pc.MaxTLSDur, pc.DefaultTLSDur, err = durationsToCertificates(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if sc := c.Ssh; sc != nil {
|
||||
pc.EnableSSHCA = &sc.Enabled
|
||||
if d := sc.UserDurations; d != nil {
|
||||
pc.MinUserSSHDur, pc.MaxUserSSHDur, pc.DefaultUserSSHDur, err = durationsToCertificates(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if d := sc.HostDurations; d != nil {
|
||||
pc.MinHostSSHDur, pc.MaxHostSSHDur, pc.DefaultHostSSHDur, err = durationsToCertificates(d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
// ProvisionerToCertificates converts the linkedca provisioner type to the certificates provisioner
|
||||
// interface.
|
||||
func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) {
|
||||
claims, err := claimsToCertificates(p.Claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
details := p.Details.GetData()
|
||||
if details == nil {
|
||||
return nil, fmt.Errorf("provisioner does not have any details")
|
||||
}
|
||||
|
||||
options := optionsToCertificates(p)
|
||||
|
||||
switch d := details.(type) {
|
||||
case *linkedca.ProvisionerDetails_JWK:
|
||||
jwk := new(jose.JSONWebKey)
|
||||
if err := json.Unmarshal(d.JWK.PublicKey, &jwk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &provisioner.JWK{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
Key: jwk,
|
||||
EncryptedKey: string(d.JWK.EncryptedPrivateKey),
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_X5C:
|
||||
var roots []byte
|
||||
for i, root := range d.X5C.GetRoots() {
|
||||
if i > 0 {
|
||||
roots = append(roots, '\n')
|
||||
}
|
||||
roots = append(roots, root...)
|
||||
}
|
||||
return &provisioner.X5C{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
Roots: roots,
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_K8SSA:
|
||||
var publicKeys []byte
|
||||
for i, k := range d.K8SSA.GetPublicKeys() {
|
||||
if i > 0 {
|
||||
publicKeys = append(publicKeys, '\n')
|
||||
}
|
||||
publicKeys = append(publicKeys, k...)
|
||||
}
|
||||
return &provisioner.K8sSA{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
PubKeys: publicKeys,
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_SSHPOP:
|
||||
return &provisioner.SSHPOP{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
Claims: claims,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_ACME:
|
||||
cfg := d.ACME
|
||||
return &provisioner.ACME{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
ForceCN: cfg.ForceCn,
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_OIDC:
|
||||
cfg := d.OIDC
|
||||
return &provisioner.OIDC{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
TenantID: cfg.TenantId,
|
||||
ClientID: cfg.ClientId,
|
||||
ClientSecret: cfg.ClientSecret,
|
||||
ConfigurationEndpoint: cfg.ConfigurationEndpoint,
|
||||
Admins: cfg.Admins,
|
||||
Domains: cfg.Domains,
|
||||
Groups: cfg.Groups,
|
||||
ListenAddress: cfg.ListenAddress,
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_AWS:
|
||||
cfg := d.AWS
|
||||
instanceAge, err := parseInstanceAge(cfg.InstanceAge)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &provisioner.AWS{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
Accounts: cfg.Accounts,
|
||||
DisableCustomSANs: cfg.DisableCustomSans,
|
||||
DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse,
|
||||
InstanceAge: instanceAge,
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_GCP:
|
||||
cfg := d.GCP
|
||||
instanceAge, err := parseInstanceAge(cfg.InstanceAge)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &provisioner.GCP{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
ServiceAccounts: cfg.ServiceAccounts,
|
||||
ProjectIDs: cfg.ProjectIds,
|
||||
DisableCustomSANs: cfg.DisableCustomSans,
|
||||
DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse,
|
||||
InstanceAge: instanceAge,
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
case *linkedca.ProvisionerDetails_Azure:
|
||||
cfg := d.Azure
|
||||
return &provisioner.Azure{
|
||||
ID: p.Id,
|
||||
Type: p.Type.String(),
|
||||
Name: p.Name,
|
||||
TenantID: cfg.TenantId,
|
||||
ResourceGroups: cfg.ResourceGroups,
|
||||
Audience: cfg.Audience,
|
||||
DisableCustomSANs: cfg.DisableCustomSans,
|
||||
DisableTrustOnFirstUse: cfg.DisableTrustOnFirstUse,
|
||||
Claims: claims,
|
||||
Options: options,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("provisioner %s not implemented", p.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func parseInstanceAge(age string) (provisioner.Duration, error) {
|
||||
var instanceAge provisioner.Duration
|
||||
if age != "" {
|
||||
iap, err := provisioner.NewDuration(age)
|
||||
if err != nil {
|
||||
return instanceAge, err
|
||||
}
|
||||
instanceAge = *iap
|
||||
}
|
||||
return instanceAge, nil
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ func TestGetEncryptedKey(t *testing.T) {
|
|||
}
|
||||
} else {
|
||||
if assert.Nil(t, tc.err) {
|
||||
val, ok := tc.a.provisioners.Load("max:" + tc.kid)
|
||||
val, ok := tc.a.provisioners.Load("mike:" + tc.kid)
|
||||
assert.Fatal(t, ok)
|
||||
p, ok := val.(*provisioner.JWK)
|
||||
assert.Fatal(t, ok)
|
||||
|
|
104
authority/ssh.go
104
authority/ssh.go
|
@ -10,11 +10,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"github.com/smallstep/certificates/templates"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/randutil"
|
||||
"go.step.sm/crypto/sshutil"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
@ -32,103 +32,17 @@ const (
|
|||
SSHAddUserCommand = "sudo useradd -m <principal>; nc -q0 localhost 22"
|
||||
)
|
||||
|
||||
// SSHConfig contains the user and host keys.
|
||||
type SSHConfig struct {
|
||||
HostKey string `json:"hostKey"`
|
||||
UserKey string `json:"userKey"`
|
||||
Keys []*SSHPublicKey `json:"keys,omitempty"`
|
||||
AddUserPrincipal string `json:"addUserPrincipal,omitempty"`
|
||||
AddUserCommand string `json:"addUserCommand,omitempty"`
|
||||
Bastion *Bastion `json:"bastion,omitempty"`
|
||||
}
|
||||
|
||||
// Bastion contains the custom properties used on bastion.
|
||||
type Bastion struct {
|
||||
Hostname string `json:"hostname"`
|
||||
User string `json:"user,omitempty"`
|
||||
Port string `json:"port,omitempty"`
|
||||
Command string `json:"cmd,omitempty"`
|
||||
Flags string `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
// HostTag are tagged with k,v pairs. These tags are how a user is ultimately
|
||||
// associated with a host.
|
||||
type HostTag struct {
|
||||
ID string
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Host defines expected attributes for an ssh host.
|
||||
type Host struct {
|
||||
HostID string `json:"hid"`
|
||||
HostTags []HostTag `json:"host_tags"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
// Validate checks the fields in SSHConfig.
|
||||
func (c *SSHConfig) Validate() error {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
for _, k := range c.Keys {
|
||||
if err := k.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SSHPublicKey contains a public key used by federated CAs to keep old signing
|
||||
// keys for this ca.
|
||||
type SSHPublicKey struct {
|
||||
Type string `json:"type"`
|
||||
Federated bool `json:"federated"`
|
||||
Key jose.JSONWebKey `json:"key"`
|
||||
publicKey ssh.PublicKey
|
||||
}
|
||||
|
||||
// Validate checks the fields in SSHPublicKey.
|
||||
func (k *SSHPublicKey) Validate() error {
|
||||
switch {
|
||||
case k.Type == "":
|
||||
return errors.New("type cannot be empty")
|
||||
case k.Type != provisioner.SSHHostCert && k.Type != provisioner.SSHUserCert:
|
||||
return errors.Errorf("invalid type %s, it must be user or host", k.Type)
|
||||
case !k.Key.IsPublic():
|
||||
return errors.New("invalid key type, it must be a public key")
|
||||
}
|
||||
|
||||
key, err := ssh.NewPublicKey(k.Key.Key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating ssh key")
|
||||
}
|
||||
k.publicKey = key
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublicKey returns the ssh public key.
|
||||
func (k *SSHPublicKey) PublicKey() ssh.PublicKey {
|
||||
return k.publicKey
|
||||
}
|
||||
|
||||
// SSHKeys represents the SSH User and Host public keys.
|
||||
type SSHKeys struct {
|
||||
UserKeys []ssh.PublicKey
|
||||
HostKeys []ssh.PublicKey
|
||||
}
|
||||
|
||||
// GetSSHRoots returns the SSH User and Host public keys.
|
||||
func (a *Authority) GetSSHRoots(context.Context) (*SSHKeys, error) {
|
||||
return &SSHKeys{
|
||||
func (a *Authority) GetSSHRoots(context.Context) (*config.SSHKeys, error) {
|
||||
return &config.SSHKeys{
|
||||
HostKeys: a.sshCAHostCerts,
|
||||
UserKeys: a.sshCAUserCerts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSSHFederation returns the public keys for federated SSH signers.
|
||||
func (a *Authority) GetSSHFederation(context.Context) (*SSHKeys, error) {
|
||||
return &SSHKeys{
|
||||
func (a *Authority) GetSSHFederation(context.Context) (*config.SSHKeys, error) {
|
||||
return &config.SSHKeys{
|
||||
HostKeys: a.sshCAHostFederatedCerts,
|
||||
UserKeys: a.sshCAUserFederatedCerts,
|
||||
}, nil
|
||||
|
@ -194,7 +108,7 @@ func (a *Authority) GetSSHConfig(ctx context.Context, typ string, data map[strin
|
|||
|
||||
// GetSSHBastion returns the bastion configuration, for the given pair user,
|
||||
// hostname.
|
||||
func (a *Authority) GetSSHBastion(ctx context.Context, user string, hostname string) (*Bastion, error) {
|
||||
func (a *Authority) GetSSHBastion(ctx context.Context, user string, hostname string) (*config.Bastion, error) {
|
||||
if a.sshBastionFunc != nil {
|
||||
bs, err := a.sshBastionFunc(ctx, user, hostname)
|
||||
return bs, errs.Wrap(http.StatusInternalServerError, err, "authority.GetSSHBastion")
|
||||
|
@ -568,7 +482,7 @@ func (a *Authority) CheckSSHHost(ctx context.Context, principal string, token st
|
|||
}
|
||||
|
||||
// GetSSHHosts returns a list of valid host principals.
|
||||
func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]Host, error) {
|
||||
func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]config.Host, error) {
|
||||
if a.sshGetHostsFunc != nil {
|
||||
hosts, err := a.sshGetHostsFunc(ctx, cert)
|
||||
return hosts, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts")
|
||||
|
@ -578,9 +492,9 @@ func (a *Authority) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]
|
|||
return nil, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts")
|
||||
}
|
||||
|
||||
hosts := make([]Host, len(hostnames))
|
||||
hosts := make([]config.Host, len(hostnames))
|
||||
for i, hn := range hostnames {
|
||||
hosts[i] = Host{Hostname: hn}
|
||||
hosts[i] = config.Host{Hostname: hn}
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
|
|
@ -590,69 +590,6 @@ func TestSSHConfig_Validate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSSHPublicKey_Validate(t *testing.T) {
|
||||
key, err := jose.GenerateJWK("EC", "P-256", "", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type fields struct {
|
||||
Type string
|
||||
Federated bool
|
||||
Key jose.JSONWebKey
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantErr bool
|
||||
}{
|
||||
{"user", fields{"user", true, key.Public()}, false},
|
||||
{"host", fields{"host", false, key.Public()}, false},
|
||||
{"empty", fields{"", true, key.Public()}, true},
|
||||
{"badType", fields{"bad", false, key.Public()}, true},
|
||||
{"badKey", fields{"user", false, *key}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
k := &SSHPublicKey{
|
||||
Type: tt.fields.Type,
|
||||
Federated: tt.fields.Federated,
|
||||
Key: tt.fields.Key,
|
||||
}
|
||||
if err := k.Validate(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("SSHPublicKey.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHPublicKey_PublicKey(t *testing.T) {
|
||||
key, err := jose.GenerateJWK("EC", "P-256", "", "sig", "", 0)
|
||||
assert.FatalError(t, err)
|
||||
pub, err := ssh.NewPublicKey(key.Public().Key)
|
||||
assert.FatalError(t, err)
|
||||
|
||||
type fields struct {
|
||||
publicKey ssh.PublicKey
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want ssh.PublicKey
|
||||
}{
|
||||
{"ok", fields{pub}, pub},
|
||||
{"nil", fields{nil}, nil},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
k := &SSHPublicKey{
|
||||
publicKey: tt.fields.publicKey,
|
||||
}
|
||||
if got := k.PublicKey(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("SSHPublicKey.PublicKey() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthority_GetSSHBastion(t *testing.T) {
|
||||
bastion := &Bastion{
|
||||
Hostname: "bastion.local",
|
||||
|
|
11
authority/status/status.go
Normal file
11
authority/status/status.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package status
|
||||
|
||||
// Type is the type for status.
|
||||
type Type string
|
||||
|
||||
var (
|
||||
// Active active
|
||||
Active = Type("active")
|
||||
// Deleted deleted
|
||||
Deleted = Type("deleted")
|
||||
)
|
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
casapi "github.com/smallstep/certificates/cas/apiv1"
|
||||
"github.com/smallstep/certificates/db"
|
||||
|
@ -23,14 +24,14 @@ import (
|
|||
)
|
||||
|
||||
// GetTLSOptions returns the tls options configured.
|
||||
func (a *Authority) GetTLSOptions() *TLSOptions {
|
||||
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}
|
||||
|
||||
func withDefaultASN1DN(def *ASN1DN) provisioner.CertificateModifierFunc {
|
||||
func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc {
|
||||
return func(crt *x509.Certificate, opts provisioner.SignOptions) error {
|
||||
if def == nil {
|
||||
return errors.New("default ASN1DN template cannot be nil")
|
||||
|
@ -361,10 +362,9 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
|
|||
}
|
||||
|
||||
// This method will also validate the audiences for JWK provisioners.
|
||||
var ok bool
|
||||
p, ok = a.provisioners.LoadByToken(token, &claims.Claims)
|
||||
if !ok {
|
||||
return errs.InternalServer("authority.Revoke; provisioner not found", opts...)
|
||||
p, err = a.LoadProvisionerByToken(token, &claims.Claims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rci.ProvisionerID = p.GetID()
|
||||
rci.TokenID, err = p.GetTokenID(revokeOpts.OTT)
|
||||
|
|
|
@ -656,7 +656,7 @@ func TestAuthority_Renew(t *testing.T) {
|
|||
"fail/unauthorized": func() (*renewTest, error) {
|
||||
return &renewTest{
|
||||
cert: certNoRenew,
|
||||
err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"),
|
||||
err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner 'dev'"),
|
||||
code: http.StatusUnauthorized,
|
||||
}, nil
|
||||
},
|
||||
|
@ -856,7 +856,7 @@ func TestAuthority_Rekey(t *testing.T) {
|
|||
"fail/unauthorized": func() (*renewTest, error) {
|
||||
return &renewTest{
|
||||
cert: certNoRenew,
|
||||
err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner dev:IMi94WBNI6gP5cNHXlZYNUzvMjGdHyBRmFoo-lCEaqk"),
|
||||
err: errors.New("authority.Rekey: authority.authorizeRenew: jwk.AuthorizeRenew; renew is disabled for jwk provisioner 'dev'"),
|
||||
code: http.StatusUnauthorized,
|
||||
}, nil
|
||||
},
|
||||
|
|
568
ca/adminClient.go
Normal file
568
ca/adminClient.go
Normal file
|
@ -0,0 +1,568 @@
|
|||
package ca
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
adminAPI "github.com/smallstep/certificates/authority/admin/api"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/cli-utils/token"
|
||||
"go.step.sm/cli-utils/token/provision"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/randutil"
|
||||
"go.step.sm/linkedca"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
var adminURLPrefix = "admin"
|
||||
|
||||
// AdminClient implements an HTTP client for the CA server.
|
||||
type AdminClient struct {
|
||||
client *uaClient
|
||||
endpoint *url.URL
|
||||
retryFunc RetryFunc
|
||||
opts []ClientOption
|
||||
x5cJWK *jose.JSONWebKey
|
||||
x5cCertFile string
|
||||
x5cCertStrs []string
|
||||
x5cCert *x509.Certificate
|
||||
x5cIssuer string
|
||||
x5cSubject string
|
||||
}
|
||||
|
||||
// NewAdminClient creates a new AdminClient with the given endpoint and options.
|
||||
func NewAdminClient(endpoint string, opts ...ClientOption) (*AdminClient, error) {
|
||||
u, err := parseEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Retrieve transport from options.
|
||||
o := new(clientOptions)
|
||||
if err := o.apply(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr, err := o.getTransport(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AdminClient{
|
||||
client: newClient(tr),
|
||||
endpoint: u,
|
||||
retryFunc: o.retryFunc,
|
||||
opts: opts,
|
||||
x5cJWK: o.x5cJWK,
|
||||
x5cCertFile: o.x5cCertFile,
|
||||
x5cCertStrs: o.x5cCertStrs,
|
||||
x5cCert: o.x5cCert,
|
||||
x5cIssuer: o.x5cIssuer,
|
||||
x5cSubject: o.x5cSubject,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *AdminClient) generateAdminToken(path string) (string, error) {
|
||||
// A random jwt id will be used to identify duplicated tokens
|
||||
jwtID, err := randutil.Hex(64) // 256 bits
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
tokOptions := []token.Options{
|
||||
token.WithJWTID(jwtID),
|
||||
token.WithKid(c.x5cJWK.KeyID),
|
||||
token.WithIssuer(c.x5cIssuer),
|
||||
token.WithAudience(path),
|
||||
token.WithValidity(now, now.Add(token.DefaultValidity)),
|
||||
token.WithX5CCerts(c.x5cCertStrs),
|
||||
}
|
||||
|
||||
tok, err := provision.New(c.x5cSubject, tokOptions...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tok.SignedString(c.x5cJWK.Algorithm, c.x5cJWK.Key)
|
||||
|
||||
}
|
||||
|
||||
func (c *AdminClient) retryOnError(r *http.Response) bool {
|
||||
if c.retryFunc != nil {
|
||||
if c.retryFunc(r.StatusCode) {
|
||||
o := new(clientOptions)
|
||||
if err := o.apply(c.opts); err != nil {
|
||||
return false
|
||||
}
|
||||
tr, err := o.getTransport(c.endpoint.String())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
r.Body.Close()
|
||||
c.client.SetTransport(tr)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetAdmin performs the GET /admin/admin/{id} request to the CA.
|
||||
func (c *AdminClient) GetAdmin(id string) (*linkedca.Admin, error) {
|
||||
var retried bool
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
|
||||
retry:
|
||||
resp, err := c.client.Get(u.String())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return nil, readAdminError(resp.Body)
|
||||
}
|
||||
var adm = new(linkedca.Admin)
|
||||
if err := readProtoJSON(resp.Body, adm); err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", u)
|
||||
}
|
||||
return adm, nil
|
||||
}
|
||||
|
||||
// AdminOption is the type of options passed to the Admin method.
|
||||
type AdminOption func(o *adminOptions) error
|
||||
|
||||
type adminOptions struct {
|
||||
cursor string
|
||||
limit int
|
||||
}
|
||||
|
||||
func (o *adminOptions) apply(opts []AdminOption) (err error) {
|
||||
for _, fn := range opts {
|
||||
if err = fn(o); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *adminOptions) rawQuery() string {
|
||||
v := url.Values{}
|
||||
if len(o.cursor) > 0 {
|
||||
v.Set("cursor", o.cursor)
|
||||
}
|
||||
if o.limit > 0 {
|
||||
v.Set("limit", strconv.Itoa(o.limit))
|
||||
}
|
||||
return v.Encode()
|
||||
}
|
||||
|
||||
// WithAdminCursor will request the admins starting with the given cursor.
|
||||
func WithAdminCursor(cursor string) AdminOption {
|
||||
return func(o *adminOptions) error {
|
||||
o.cursor = cursor
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAdminLimit will request the given number of admins.
|
||||
func WithAdminLimit(limit int) AdminOption {
|
||||
return func(o *adminOptions) error {
|
||||
o.limit = limit
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetAdminsPaginate returns a page from the the GET /admin/admins request to the CA.
|
||||
func (c *AdminClient) GetAdminsPaginate(opts ...AdminOption) (*adminAPI.GetAdminsResponse, error) {
|
||||
var retried bool
|
||||
o := new(adminOptions)
|
||||
if err := o.apply(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{
|
||||
Path: "/admin/admins",
|
||||
RawQuery: o.rawQuery(),
|
||||
})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "create GET %s request failed", u)
|
||||
}
|
||||
req.Header.Add("Authorization", tok)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return nil, readAdminError(resp.Body)
|
||||
}
|
||||
var body = new(adminAPI.GetAdminsResponse)
|
||||
if err := readJSON(resp.Body, body); err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", u)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// GetAdmins returns all admins from the GET /admin/admins request to the CA.
|
||||
func (c *AdminClient) GetAdmins(opts ...AdminOption) ([]*linkedca.Admin, error) {
|
||||
var (
|
||||
cursor = ""
|
||||
admins = []*linkedca.Admin{}
|
||||
)
|
||||
for {
|
||||
resp, err := c.GetAdminsPaginate(WithAdminCursor(cursor), WithAdminLimit(100))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
admins = append(admins, resp.Admins...)
|
||||
if resp.NextCursor == "" {
|
||||
return admins, nil
|
||||
}
|
||||
cursor = resp.NextCursor
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAdmin performs the POST /admin/admins request to the CA.
|
||||
func (c *AdminClient) CreateAdmin(createAdminRequest *adminAPI.CreateAdminRequest) (*linkedca.Admin, error) {
|
||||
var retried bool
|
||||
body, err := json.Marshal(createAdminRequest)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: "/admin/admins"})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "create GET %s request failed", u)
|
||||
}
|
||||
req.Header.Add("Authorization", tok)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return nil, readAdminError(resp.Body)
|
||||
}
|
||||
var adm = new(linkedca.Admin)
|
||||
if err := readProtoJSON(resp.Body, adm); err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", u)
|
||||
}
|
||||
return adm, nil
|
||||
}
|
||||
|
||||
// RemoveAdmin performs the DELETE /admin/admins/{id} request to the CA.
|
||||
func (c *AdminClient) RemoveAdmin(id string) error {
|
||||
var retried bool
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
req, err := http.NewRequest("DELETE", u.String(), nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create DELETE %s request failed", u)
|
||||
}
|
||||
req.Header.Add("Authorization", tok)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "client DELETE %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return readAdminError(resp.Body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateAdmin performs the PUT /admin/admins/{id} request to the CA.
|
||||
func (c *AdminClient) UpdateAdmin(id string, uar *adminAPI.UpdateAdminRequest) (*linkedca.Admin, error) {
|
||||
var retried bool
|
||||
body, err := json.Marshal(uar)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "admins", id)})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
req, err := http.NewRequest("PATCH", u.String(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "create PUT %s request failed", u)
|
||||
}
|
||||
req.Header.Add("Authorization", tok)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client PUT %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return nil, readAdminError(resp.Body)
|
||||
}
|
||||
var adm = new(linkedca.Admin)
|
||||
if err := readProtoJSON(resp.Body, adm); err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", u)
|
||||
}
|
||||
return adm, nil
|
||||
}
|
||||
|
||||
// GetProvisioner performs the GET /admin/provisioners/{name} request to the CA.
|
||||
func (c *AdminClient) GetProvisioner(opts ...ProvisionerOption) (*linkedca.Provisioner, error) {
|
||||
var retried bool
|
||||
o := new(provisionerOptions)
|
||||
if err := o.apply(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var u *url.URL
|
||||
if len(o.id) > 0 {
|
||||
u = c.endpoint.ResolveReference(&url.URL{
|
||||
Path: "/admin/provisioners/id",
|
||||
RawQuery: o.rawQuery(),
|
||||
})
|
||||
} else if len(o.name) > 0 {
|
||||
u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.name)})
|
||||
} else {
|
||||
return nil, errors.New("must set either name or id in method options")
|
||||
}
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "create PUT %s request failed", u)
|
||||
}
|
||||
req.Header.Add("Authorization", tok)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return nil, readAdminError(resp.Body)
|
||||
}
|
||||
var prov = new(linkedca.Provisioner)
|
||||
if err := readProtoJSON(resp.Body, prov); err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", u)
|
||||
}
|
||||
return prov, nil
|
||||
}
|
||||
|
||||
// GetProvisionersPaginate performs the GET /admin/provisioners request to the CA.
|
||||
func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*adminAPI.GetProvisionersResponse, error) {
|
||||
var retried bool
|
||||
o := new(provisionerOptions)
|
||||
if err := o.apply(opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{
|
||||
Path: "/admin/provisioners",
|
||||
RawQuery: o.rawQuery(),
|
||||
})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "create PUT %s request failed", u)
|
||||
}
|
||||
req.Header.Add("Authorization", tok)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client GET %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return nil, readAdminError(resp.Body)
|
||||
}
|
||||
var body = new(adminAPI.GetProvisionersResponse)
|
||||
if err := readJSON(resp.Body, body); err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", u)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// GetProvisioners returns all admins from the GET /admin/admins request to the CA.
|
||||
func (c *AdminClient) GetProvisioners(opts ...AdminOption) (provisioner.List, error) {
|
||||
var (
|
||||
cursor = ""
|
||||
provs = provisioner.List{}
|
||||
)
|
||||
for {
|
||||
resp, err := c.GetProvisionersPaginate(WithProvisionerCursor(cursor), WithProvisionerLimit(100))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provs = append(provs, resp.Provisioners...)
|
||||
if resp.NextCursor == "" {
|
||||
return provs, nil
|
||||
}
|
||||
cursor = resp.NextCursor
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveProvisioner performs the DELETE /admin/provisioners/{name} request to the CA.
|
||||
func (c *AdminClient) RemoveProvisioner(opts ...ProvisionerOption) error {
|
||||
var (
|
||||
u *url.URL
|
||||
retried bool
|
||||
)
|
||||
|
||||
o := new(provisionerOptions)
|
||||
if err := o.apply(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(o.id) > 0 {
|
||||
u = c.endpoint.ResolveReference(&url.URL{
|
||||
Path: path.Join(adminURLPrefix, "provisioners/id"),
|
||||
RawQuery: o.rawQuery(),
|
||||
})
|
||||
} else if len(o.name) > 0 {
|
||||
u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.name)})
|
||||
} else {
|
||||
return errors.New("must set either name or id in method options")
|
||||
}
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
req, err := http.NewRequest("DELETE", u.String(), nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create DELETE %s request failed", u)
|
||||
}
|
||||
req.Header.Add("Authorization", tok)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "client DELETE %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return readAdminError(resp.Body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateProvisioner performs the POST /admin/provisioners request to the CA.
|
||||
func (c *AdminClient) CreateProvisioner(prov *linkedca.Provisioner) (*linkedca.Provisioner, error) {
|
||||
var retried bool
|
||||
body, err := protojson.Marshal(prov)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners")})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "create POST %s request failed", u)
|
||||
}
|
||||
req.Header.Add("Authorization", tok)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "client POST %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return nil, readAdminError(resp.Body)
|
||||
}
|
||||
var nuProv = new(linkedca.Provisioner)
|
||||
if err := readProtoJSON(resp.Body, nuProv); err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading %s", u)
|
||||
}
|
||||
return nuProv, nil
|
||||
}
|
||||
|
||||
// UpdateProvisioner performs the PUT /admin/provisioners/{name} request to the CA.
|
||||
func (c *AdminClient) UpdateProvisioner(name string, prov *linkedca.Provisioner) error {
|
||||
var retried bool
|
||||
body, err := protojson.Marshal(prov)
|
||||
if err != nil {
|
||||
return errs.Wrap(http.StatusInternalServerError, err, "error marshaling request")
|
||||
}
|
||||
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", name)})
|
||||
tok, err := c.generateAdminToken(u.Path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error generating admin token")
|
||||
}
|
||||
req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create PUT %s request failed", u)
|
||||
}
|
||||
req.Header.Add("Authorization", tok)
|
||||
retry:
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "client PUT %s failed", u)
|
||||
}
|
||||
if resp.StatusCode >= 400 {
|
||||
if !retried && c.retryOnError(resp) {
|
||||
retried = true
|
||||
goto retry
|
||||
}
|
||||
return readAdminError(resp.Body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAdminError(r io.ReadCloser) error {
|
||||
defer r.Close()
|
||||
adminErr := new(admin.Error)
|
||||
if err := json.NewDecoder(r).Decode(adminErr); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New(adminErr.Message)
|
||||
}
|
|
@ -39,6 +39,53 @@ func Bootstrap(token string) (*Client, error) {
|
|||
return NewClient(claims.Audience[0], WithRootSHA256(claims.SHA))
|
||||
}
|
||||
|
||||
// BootstrapClient is a helper function that using the given bootstrap token
|
||||
// return an http.Client configured with a Transport prepared to do TLS
|
||||
// connections using the client certificate returned by the certificate
|
||||
// authority. By default the server will kick off a routine that will renew the
|
||||
// certificate after 2/3rd of the certificate's lifetime has expired.
|
||||
//
|
||||
// Usage:
|
||||
// // Default example with certificate rotation.
|
||||
// client, err := ca.BootstrapClient(ctx.Background(), token)
|
||||
//
|
||||
// // Example canceling automatic certificate rotation.
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel()
|
||||
// client, err := ca.BootstrapClient(ctx, token)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// resp, err := client.Get("https://internal.smallstep.com")
|
||||
func BootstrapClient(ctx context.Context, token string, options ...TLSOption) (*http.Client, error) {
|
||||
client, err := Bootstrap(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, pk, err := CreateSignRequest(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sign, err := client.Sign(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure the tlsConfig have all supported roots on RootCAs
|
||||
options = append(options, AddRootsToRootCAs())
|
||||
|
||||
transport, err := client.Transport(ctx, sign, pk, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: transport,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BootstrapServer is a helper function that using the given token returns the
|
||||
// given http.Server configured with a TLS certificate signed by the Certificate
|
||||
// Authority. By default the server will kick off a routine that will renew the
|
||||
|
@ -100,53 +147,6 @@ func BootstrapServer(ctx context.Context, token string, base *http.Server, optio
|
|||
return base, nil
|
||||
}
|
||||
|
||||
// BootstrapClient is a helper function that using the given bootstrap token
|
||||
// return an http.Client configured with a Transport prepared to do TLS
|
||||
// connections using the client certificate returned by the certificate
|
||||
// authority. By default the server will kick off a routine that will renew the
|
||||
// certificate after 2/3rd of the certificate's lifetime has expired.
|
||||
//
|
||||
// Usage:
|
||||
// // Default example with certificate rotation.
|
||||
// client, err := ca.BootstrapClient(ctx.Background(), token)
|
||||
//
|
||||
// // Example canceling automatic certificate rotation.
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel()
|
||||
// client, err := ca.BootstrapClient(ctx, token)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// resp, err := client.Get("https://internal.smallstep.com")
|
||||
func BootstrapClient(ctx context.Context, token string, options ...TLSOption) (*http.Client, error) {
|
||||
client, err := Bootstrap(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, pk, err := CreateSignRequest(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sign, err := client.Sign(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure the tlsConfig have all supported roots on RootCAs
|
||||
options = append(options, AddRootsToRootCAs())
|
||||
|
||||
transport, err := client.Transport(ctx, sign, pk, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: transport,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BootstrapListener is a helper function that using the given token returns a
|
||||
// TLS listener which accepts connections from an inner listener and wraps each
|
||||
// connection with Server.
|
||||
|
|
23
ca/ca.go
23
ca/ca.go
|
@ -17,6 +17,8 @@ import (
|
|||
acmeNoSQL "github.com/smallstep/certificates/acme/db/nosql"
|
||||
"github.com/smallstep/certificates/api"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
adminAPI "github.com/smallstep/certificates/authority/admin/api"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/certificates/logging"
|
||||
"github.com/smallstep/certificates/monitoring"
|
||||
|
@ -77,7 +79,7 @@ func WithDatabase(db db.AuthDB) Option {
|
|||
// the HTTP server, set ups the middlewares and the HTTP handlers.
|
||||
type CA struct {
|
||||
auth *authority.Authority
|
||||
config *authority.Config
|
||||
config *config.Config
|
||||
srv *server.Server
|
||||
insecureSrv *server.Server
|
||||
opts *options
|
||||
|
@ -85,7 +87,7 @@ type CA struct {
|
|||
}
|
||||
|
||||
// New creates and initializes the CA with the given configuration and options.
|
||||
func New(config *authority.Config, opts ...Option) (*CA, error) {
|
||||
func New(config *config.Config, opts ...Option) (*CA, error) {
|
||||
ca := &CA{
|
||||
config: config,
|
||||
opts: new(options),
|
||||
|
@ -95,7 +97,7 @@ func New(config *authority.Config, opts ...Option) (*CA, error) {
|
|||
}
|
||||
|
||||
// Init initializes the CA with the given configuration.
|
||||
func (ca *CA) Init(config *authority.Config) (*CA, error) {
|
||||
func (ca *CA) Init(config *config.Config) (*CA, error) {
|
||||
// Intermediate Password.
|
||||
if len(ca.opts.password) > 0 {
|
||||
ca.config.Password = string(ca.opts.password)
|
||||
|
@ -149,6 +151,7 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
|
|||
dns = fmt.Sprintf("%s:%s", dns, port)
|
||||
}
|
||||
|
||||
// ACME Router
|
||||
prefix := "acme"
|
||||
var acmeDB acme.DB
|
||||
if config.DB == nil {
|
||||
|
@ -175,6 +178,17 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
|
|||
acmeHandler.Route(r)
|
||||
})
|
||||
|
||||
// Admin API Router
|
||||
if config.AuthorityConfig.EnableAdmin {
|
||||
adminDB := auth.GetAdminDatabase()
|
||||
if adminDB != nil {
|
||||
adminHandler := adminAPI.NewHandler(auth)
|
||||
mux.Route("/admin", func(r chi.Router) {
|
||||
adminHandler.Route(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if ca.shouldServeSCEPEndpoints() {
|
||||
scepPrefix := "scep"
|
||||
scepAuthority, err := scep.New(auth, scep.AuthorityOptions{
|
||||
|
@ -245,7 +259,6 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
|
|||
|
||||
// Run starts the CA calling to the server ListenAndServe method.
|
||||
func (ca *CA) Run() error {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 1)
|
||||
|
||||
|
@ -293,7 +306,7 @@ func (ca *CA) Stop() error {
|
|||
// Reload reloads the configuration of the CA and calls to the server Reload
|
||||
// method.
|
||||
func (ca *CA) Reload() error {
|
||||
config, err := authority.LoadConfiguration(ca.opts.configFile)
|
||||
config, err := config.LoadConfiguration(ca.opts.configFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error reloading ca configuration")
|
||||
}
|
||||
|
|
110
ca/client.go
110
ca/client.go
|
@ -10,8 +10,10 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -28,10 +30,13 @@ import (
|
|||
"github.com/smallstep/certificates/ca/identity"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
"go.step.sm/cli-utils/config"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/pemutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
"golang.org/x/net/http2"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
|
@ -88,6 +93,11 @@ func (c *uaClient) Post(url, contentType string, body io.Reader) (*http.Response
|
|||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *uaClient) Do(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set("User-Agent", UserAgent)
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
// RetryFunc defines the method used to retry a request. If it returns true, the
|
||||
// request will be retried once.
|
||||
type RetryFunc func(code int) bool
|
||||
|
@ -103,6 +113,12 @@ type clientOptions struct {
|
|||
certificate tls.Certificate
|
||||
getClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
|
||||
retryFunc RetryFunc
|
||||
x5cJWK *jose.JSONWebKey
|
||||
x5cCertFile string
|
||||
x5cCertStrs []string
|
||||
x5cCert *x509.Certificate
|
||||
x5cIssuer string
|
||||
x5cSubject string
|
||||
}
|
||||
|
||||
func (o *clientOptions) apply(opts []ClientOption) (err error) {
|
||||
|
@ -261,9 +277,66 @@ func WithCABundle(bundle []byte) ClientOption {
|
|||
|
||||
// WithCertificate will set the given certificate as the TLS client certificate
|
||||
// in the client.
|
||||
func WithCertificate(crt tls.Certificate) ClientOption {
|
||||
func WithCertificate(cert tls.Certificate) ClientOption {
|
||||
return func(o *clientOptions) error {
|
||||
o.certificate = crt
|
||||
o.certificate = cert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
stepOIDRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64}
|
||||
stepOIDProvisioner = append(asn1.ObjectIdentifier(nil), append(stepOIDRoot, 1)...)
|
||||
)
|
||||
|
||||
type stepProvisionerASN1 struct {
|
||||
Type int
|
||||
Name []byte
|
||||
CredentialID []byte
|
||||
KeyValuePairs []string `asn1:"optional,omitempty"`
|
||||
}
|
||||
|
||||
// WithAdminX5C will set the given file as the X5C certificate for use
|
||||
// by the client.
|
||||
func WithAdminX5C(certs []*x509.Certificate, key interface{}, passwordFile string) ClientOption {
|
||||
return func(o *clientOptions) error {
|
||||
// Get private key from given key file
|
||||
var (
|
||||
err error
|
||||
opts []jose.Option
|
||||
)
|
||||
if len(passwordFile) != 0 {
|
||||
opts = append(opts, jose.WithPasswordFile(passwordFile))
|
||||
}
|
||||
blk, err := pemutil.Serialize(key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error serializing private key")
|
||||
}
|
||||
o.x5cJWK, err = jose.ParseKey(pem.EncodeToMemory(blk), opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.x5cCertStrs, err = jose.ValidateX5C(certs, o.x5cJWK.Key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error validating x5c certificate chain and key for use in x5c header")
|
||||
}
|
||||
|
||||
o.x5cCert = certs[0]
|
||||
o.x5cSubject = o.x5cCert.Subject.CommonName
|
||||
|
||||
for _, e := range o.x5cCert.Extensions {
|
||||
if e.Id.Equal(stepOIDProvisioner) {
|
||||
var provisioner stepProvisionerASN1
|
||||
if _, err := asn1.Unmarshal(e.Value, &provisioner); err != nil {
|
||||
return errors.Wrap(err, "error unmarshaling provisioner OID from certificate")
|
||||
}
|
||||
o.x5cIssuer = string(provisioner.Name)
|
||||
}
|
||||
}
|
||||
if len(o.x5cIssuer) == 0 {
|
||||
return errors.New("provisioner extension not found in certificate")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -367,6 +440,8 @@ type ProvisionerOption func(o *provisionerOptions) error
|
|||
type provisionerOptions struct {
|
||||
cursor string
|
||||
limit int
|
||||
id string
|
||||
name string
|
||||
}
|
||||
|
||||
func (o *provisionerOptions) apply(opts []ProvisionerOption) (err error) {
|
||||
|
@ -386,6 +461,12 @@ func (o *provisionerOptions) rawQuery() string {
|
|||
if o.limit > 0 {
|
||||
v.Set("limit", strconv.Itoa(o.limit))
|
||||
}
|
||||
if len(o.id) > 0 {
|
||||
v.Set("id", o.id)
|
||||
}
|
||||
if len(o.name) > 0 {
|
||||
v.Set("name", o.name)
|
||||
}
|
||||
return v.Encode()
|
||||
}
|
||||
|
||||
|
@ -405,6 +486,22 @@ func WithProvisionerLimit(limit int) ProvisionerOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithProvisionerID will request the given provisioner.
|
||||
func WithProvisionerID(id string) ProvisionerOption {
|
||||
return func(o *provisionerOptions) error {
|
||||
o.id = id
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithProvisionerName will request the given provisioner.
|
||||
func WithProvisionerName(name string) ProvisionerOption {
|
||||
return func(o *provisionerOptions) error {
|
||||
o.name = name
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Client implements an HTTP client for the CA server.
|
||||
type Client struct {
|
||||
client *uaClient
|
||||
|
@ -1206,6 +1303,15 @@ func readJSON(r io.ReadCloser, v interface{}) error {
|
|||
return json.NewDecoder(r).Decode(v)
|
||||
}
|
||||
|
||||
func readProtoJSON(r io.ReadCloser, m proto.Message) error {
|
||||
defer r.Close()
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return protojson.Unmarshal(data, m)
|
||||
}
|
||||
|
||||
func readError(r io.ReadCloser) error {
|
||||
defer r.Close()
|
||||
apiErr := new(errs.Error)
|
||||
|
|
4
ca/testdata/ca.json
vendored
4
ca/testdata/ca.json
vendored
|
@ -34,7 +34,7 @@
|
|||
"y": "ZhYcFQBqtErdC_pA7sOXrO7AboCEPIKP9Ik4CHJqANk"
|
||||
}
|
||||
}, {
|
||||
"name": "max",
|
||||
"name": "mike",
|
||||
"type": "jwk",
|
||||
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlZsWnl0dUxrWTR5enlqZXJybnN0aGcifQ.QP15wQYjZ12BLgl-XTq2Vb12G3OHAfic.X35QqAaXwnlmeCUU._2qIUp0TI8yDI7c2e9upIRdrnmB5OvtLfrYN-Su2NLBpaoYtr9O55Wo0Iryc0W2pYqnVDPvgPPes4P4nQAnzw5WhFYc1Xf1ZEetfdNhwi1x2FNwPbACBAgxm5AW40O5AAlbLcWushYASfeMBZocTGXuSGUzwFqoWD-5EDJ80TWQ7cAj3ttHrJ_3QV9hi4O9KJUCiXngN-Yz2zXrhBL4NOH2fmRbaf5c0rF8xUJIIW-TcyYJeX_Fbx1IzzKKPd9USUwkDhxD4tLa51I345xVqjuwG1PEn6nF8JKqLRVUKEKFin-ShXrfE61KceyAvm4YhWKrbJWIm3bH5Hxaphy4.TexIrIhsRxJStpE3EJ925Q",
|
||||
"key": {
|
||||
|
@ -76,7 +76,7 @@
|
|||
"minTLSCertDuration": "1s"
|
||||
}
|
||||
}, {
|
||||
"name": "mariano",
|
||||
"name": "maxey",
|
||||
"type": "jwk",
|
||||
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6Ik5SLTk5ZkVMSm1CLW1FZGllUlFFc3cifQ.Fr314BEUGTda4ICJl2uxFdjpEUGGqJEV.gBbu_DZE1ONDu14r.X-7MKMyokZIF1HTCVqqL0tTWgaC1ZGZBLLltd11ZUhQTswo_8kvgiTv3cFShj7ATF0tAY8HStyJmzLO8mKPVOPDXSwjdNsPriZclI6JWGi9iOu8pEiN9pZM6-itxan1JMcDUNg2U-P1BmKppHRbDKsOTivymfRyeUk51dBIlS54p5xNK1HFLc1YtWC1Rc_ngYVqOgqlhIrCHArAEBe3jrfUaH2ym-8fkVdwVqtxmte3XXK9g8FchsygRNnOKtRcr0TyzTUV-7bPi8_t02Zi-EHLFaSawVXWV_Qk1GeLYJR22Rp74beo-b5-lCNVp10btO0xdGySUWmCJ4v4_QZw.c8unwWycwtfdJMM_0b0fuA",
|
||||
"key": {
|
||||
|
|
|
@ -103,7 +103,6 @@ func (c *Client) getClientTLSConfig(ctx context.Context, sign *api.SignResponse,
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Update renew function with transport
|
||||
tr := getDefaultTransport(tlsConfig)
|
||||
// Use mutable tls.Config on renew
|
||||
tr.DialTLS = c.buildDialTLS(tlsCtx) // nolint:staticcheck
|
||||
|
|
|
@ -143,7 +143,11 @@ func newX5CSigner(certFile, keyFile, password string) (jose.Signer, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certs, err := jose.ValidateX5C(certFile, signer)
|
||||
certs, err := pemutil.ReadCertificateBundle(certFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error reading x5c certificate chain")
|
||||
}
|
||||
certStrs, err := jose.ValidateX5C(certs, signer)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error validating x5c certificate chain and key")
|
||||
}
|
||||
|
@ -151,7 +155,7 @@ func newX5CSigner(certFile, keyFile, password string) (jose.Signer, error) {
|
|||
so := new(jose.SignerOptions)
|
||||
so.WithType("JWT")
|
||||
so.WithHeader("kid", kid)
|
||||
so.WithHeader("x5c", certs)
|
||||
so.WithHeader("x5c", certStrs)
|
||||
return newJoseSigner(signer, so)
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"unicode"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/urfave/cli"
|
||||
"go.step.sm/cli-utils/errs"
|
||||
|
@ -56,7 +56,7 @@ func appAction(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
configFile := ctx.Args().Get(0)
|
||||
config, err := authority.LoadConfiguration(configFile)
|
||||
config, err := config.LoadConfiguration(configFile)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/smallstep/certificates/cas/apiv1"
|
||||
"github.com/smallstep/certificates/pki"
|
||||
|
@ -162,7 +162,7 @@ func onboardAction(ctx *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func onboardPKI(config onboardingConfiguration) (*authority.Config, string, error) {
|
||||
func onboardPKI(config onboardingConfiguration) (*config.Config, string, error) {
|
||||
p, err := pki.New(apiv1.Options{
|
||||
Type: apiv1.SoftCAS,
|
||||
IsCreator: true,
|
||||
|
|
17
go.mod
17
go.mod
|
@ -26,19 +26,22 @@ require (
|
|||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
github.com/urfave/cli v1.22.4
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1
|
||||
go.step.sm/cli-utils v0.2.0
|
||||
go.step.sm/crypto v0.8.3
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
|
||||
go.step.sm/cli-utils v0.4.1
|
||||
go.step.sm/crypto v0.9.0
|
||||
go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420
|
||||
google.golang.org/api v0.47.0
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c
|
||||
google.golang.org/grpc v1.38.0
|
||||
google.golang.org/protobuf v1.26.0
|
||||
gopkg.in/square/go-jose.v2 v2.5.1
|
||||
)
|
||||
|
||||
// replace github.com/smallstep/nosql => ../nosql
|
||||
// replace go.step.sm/crypto => ../crypto
|
||||
|
||||
//replace go.step.sm/crypto => ../crypto
|
||||
|
||||
//replace go.step.sm/cli-utils => ../cli-utils
|
||||
|
||||
replace go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 => github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568
|
||||
|
|
200
go.sum
200
go.sum
|
@ -45,6 +45,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
|||
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
|
@ -54,38 +55,77 @@ github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TN
|
|||
github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/ThalesIgnite/crypto11 v1.2.4 h1:3MebRK/U0mA2SmSthXAIZAdUA9w8+ZuKem2O6HuR1f8=
|
||||
github.com/ThalesIgnite/crypto11 v1.2.4 h1:3MebRK/U0mA2SmSthXAIZAdUA9w8+ZuKem2O6HuR1f8=
|
||||
github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
|
||||
github.com/ThalesIgnite/crypto11 v1.2.4/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
|
||||
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY=
|
||||
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.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0=
|
||||
github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0 h1:qZ+woO4SamnH/eEbjM2IDLhRNwIwND/RQyVlBLp3Jqg=
|
||||
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=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/casbin/casbin/v2 v2.1.2 h1:bTwon/ECRx9dwBy2ewRVr5OiqjeXSGiTUY74sDPQi/g=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
|
@ -93,23 +133,32 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O
|
|||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec h1:EdRZT3IeKQmfCSrgo8SZ8V3MEnskuJP0wCYNpe+aiXo=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf h1:CAKfRE2YtTUIjjh1bkBtyYFaUT/WmOqsJjgtihT0vMI=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
|
@ -121,6 +170,7 @@ github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20201003150343-5d1bab4fc658/go.mod h
|
|||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd h1:KoJOtZf+6wpQaDTuOWGuo61GxcPBIfhwRxRTaTWGCTc=
|
||||
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
|
@ -128,9 +178,13 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUn
|
|||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
@ -138,23 +192,32 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
|||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db h1:gb2Z18BhTPJPpLQWj4T+rfKHYCHxRHCtRxhKKjRidVw=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.4.0 h1:KeVK+Emj3c3S4eRztFuzbFYb2BAgf2jmwDwyXEri7Lo=
|
||||
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
|
||||
|
@ -164,13 +227,17 @@ github.com/go-piv/piv-go v1.7.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8H
|
|||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.6.0 h1:MmJCxYVKTJ0SplGKqFVX3SBnmaUhODHZrrFF6jMbpZk=
|
||||
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/googleapis v1.1.0 h1:kFkMAZBNAn4j7K0GiZr8cRYzejq68VbheufiV3YuyFI=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -200,6 +267,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
|||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
|
@ -221,10 +289,13 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
|
@ -249,66 +320,101 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c h1:Lh2aW+HnU2Nbe1gqD9SOJLJxW1jBMmQOktN2acDyJk8=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda h1:5ikpG9mYCMFiZX0nkxoV6aU2IpCHPdws3gCNgdZeEV0=
|
||||
github.com/groob/finalizer v0.0.0-20170707115354-4c2ed49aabda/go.mod h1:MyndkAZd5rUMdNogn35MWXBX1UiBigrU8eTj8DoAC2c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.3.0 h1:HXNYlRkkM/t+Y/Yhxtwcy02dlYwIaoxzvxPnS+cqy78=
|
||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
github.com/hashicorp/consul/sdk v0.3.0 h1:UOxjlb4xVNF93jak1mzzoBatyFju9nrkxpVwIp/QqxQ=
|
||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0 h1:WhIgCr5a7AaVH6jPUwjtRuuE7/RDufnUvzIr48smyxs=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/hudl/fargo v1.3.0 h1:0U6+BtN6LhaYuTnIJq4Wyq5cpn6O2kWrxAtcqBmYY6w=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
|
@ -316,12 +422,16 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743 h1:143Bb8f8DuGWck/xpNUOckBVYfFbBTnLevfRZ1aVVqo=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1 h1:vi1F1IQ8N7hNWytK9DpJsUfQhGuNSc19z330K6vl4zk=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13 h1:KNt/RhmQTOLr7Aj8PsJ7mTronaFyx80mRTT9qF261dA=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo=
|
||||
github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
|
||||
|
@ -336,89 +446,132 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/micromdm/scep/v2 v2.0.0 h1:cRzcY0S5QX+0+J+7YC4P2uZSnfMup8S8zJu/bLFgOkA=
|
||||
github.com/micromdm/scep/v2 v2.0.0/go.mod h1:ouaDs5tcjOjdHD/h8BGaQsWE87MUnQ/wMTMgfMMIpPc=
|
||||
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
|
||||
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2 h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||
github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/newrelic/go-agent v2.15.0+incompatible h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU=
|
||||
github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ=
|
||||
github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk=
|
||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5 h1:58+kh9C6jJVXYjt8IE48G2eWl6BjwU5Gj0gqY84fy78=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568 h1:+MPqEswjYiS0S1FCTg8MIhMBMzxiVQ94rooFwvPPiWk=
|
||||
github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/pact-foundation/pact-go v1.0.4 h1:OYkFijGHoZAYbOIb1LWXrwKQbMMRUv1oQ89blD2Mh2Q=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible h1:2WnRzIquHa5QxaJKShDkLM+sc0JPuwhXzK8OYOyt3Vg=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
|
@ -426,10 +579,13 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue
|
|||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f h1:UFr9zpz4xgTnIE5yIMtWAMngCdZ9p/+q6lTbgelo80M=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 h1:CmSpbxmewNQbzqztaY0bke1qzHhyNyC29wYgh17Gxfo=
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
|
@ -441,9 +597,13 @@ 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.3.6 h1:cq6a3NwjFJxkVlWU1T4qGskcfEXr0fO1WqQrraDO1Po=
|
||||
github.com/smallstep/nosql v0.3.6/go.mod h1:h1zC/Z54uNHc8euquLED4qJNCrMHd3nytA141ZZh4qQ=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ=
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
|
@ -453,13 +613,17 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
|
|||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a h1:AhmOdSHeswKHBjhsLs/7+1voOxT+LLrSk/Nxvk35fug=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
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=
|
||||
|
@ -472,14 +636,18 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -489,6 +657,7 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
|
@ -502,15 +671,29 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
|||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.step.sm/cli-utils v0.2.0 h1:hpVu9+6dpv/7/Bd8nGJFc3V+gQ+TciSJRTu9TavDUQ4=
|
||||
go.step.sm/cli-utils v0.2.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y=
|
||||
go.step.sm/cli-utils v0.4.0 h1:dni6gR/6/LOqfbzm/yUdgz5O12tkxX17SxA9+pRMidI=
|
||||
go.step.sm/cli-utils v0.4.0/go.mod h1:1zFgatDqEJ1Y4MNStdWa0b1NPc1fvSHbDJC+wZ6iQlE=
|
||||
go.step.sm/cli-utils v0.4.1 h1:QztRUhGYjOPM1I2Nmi7V6XejQyVtcESmo+sbegxvX7Q=
|
||||
go.step.sm/cli-utils v0.4.1/go.mod h1:hWYVOSlw8W9Pd+BwIbs/aftVVMRms3EG7Q2qLRwc0WA=
|
||||
go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0=
|
||||
go.step.sm/crypto v0.8.3 h1:TO/OPlaUrYXhs8srGEFNyL6OWVQvRmEPCUONNnQUuEM=
|
||||
go.step.sm/crypto v0.8.3/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||
go.step.sm/crypto v0.9.0 h1:q2AllTSnVj4NRtyEPkGW2ohArLmbGbe6ZAL/VIOKDzA=
|
||||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||
go.step.sm/linkedca v0.0.0-20210610014030-59b16916c7e7 h1:hAfzUm80XWGtFnxyVgeT/gc/3XnlVNnHD5HrLbk4Fc0=
|
||||
go.step.sm/linkedca v0.0.0-20210610014030-59b16916c7e7/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
||||
go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25 h1:ncJqviWswJT19IdnfOYQGKG1zL7IDy4lAJz1PuM3fgw=
|
||||
go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
@ -523,8 +706,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -603,6 +786,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
|
@ -686,6 +870,7 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -837,6 +1022,7 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d h1:KzwjikDymrEmYYbdyfievTwjEeGlu+OM6oiKBkF3Jfg=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
|
@ -881,19 +1067,27 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
|||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@ -913,5 +1107,7 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
|||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 h1:ucqkfpjg9WzSUubAO62csmucvxl4/JeW3F4I4909XkM=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
|
|
26
pki/pki.go
26
pki/pki.go
|
@ -19,7 +19,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
authconfig "github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/ca"
|
||||
"github.com/smallstep/certificates/cas"
|
||||
|
@ -481,12 +481,12 @@ type caDefaults struct {
|
|||
}
|
||||
|
||||
// Option is the type for modifiers over the auth config object.
|
||||
type Option func(c *authority.Config) error
|
||||
type Option func(c *authconfig.Config) error
|
||||
|
||||
// WithDefaultDB is a configuration modifier that adds a default DB stanza to
|
||||
// the authority config.
|
||||
func WithDefaultDB() Option {
|
||||
return func(c *authority.Config) error {
|
||||
return func(c *authconfig.Config) error {
|
||||
c.DB = &db.Config{
|
||||
Type: "badger",
|
||||
DataSource: GetDBPath(),
|
||||
|
@ -498,14 +498,14 @@ func WithDefaultDB() Option {
|
|||
// WithoutDB is a configuration modifier that adds a default DB stanza to
|
||||
// the authority config.
|
||||
func WithoutDB() Option {
|
||||
return func(c *authority.Config) error {
|
||||
return func(c *authconfig.Config) error {
|
||||
c.DB = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateConfig returns the step certificates configuration.
|
||||
func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
|
||||
func (p *PKI) GenerateConfig(opt ...Option) (*authconfig.Config, error) {
|
||||
key, err := p.ottPrivateKey.CompactSerialize()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error serializing private key")
|
||||
|
@ -523,7 +523,7 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
|
|||
authorityOptions = &p.casOptions
|
||||
}
|
||||
|
||||
config := &authority.Config{
|
||||
config := &authconfig.Config{
|
||||
Root: []string{p.root},
|
||||
FederatedRoots: []string{},
|
||||
IntermediateCert: p.intermediate,
|
||||
|
@ -535,22 +535,22 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
|
|||
Type: "badger",
|
||||
DataSource: GetDBPath(),
|
||||
},
|
||||
AuthorityConfig: &authority.AuthConfig{
|
||||
AuthorityConfig: &authconfig.AuthConfig{
|
||||
Options: authorityOptions,
|
||||
DisableIssuedAtCheck: false,
|
||||
Provisioners: provisioner.List{prov},
|
||||
},
|
||||
TLS: &authority.TLSOptions{
|
||||
MinVersion: authority.DefaultTLSMinVersion,
|
||||
MaxVersion: authority.DefaultTLSMaxVersion,
|
||||
Renegotiation: authority.DefaultTLSRenegotiation,
|
||||
CipherSuites: authority.DefaultTLSCipherSuites,
|
||||
TLS: &authconfig.TLSOptions{
|
||||
MinVersion: authconfig.DefaultTLSMinVersion,
|
||||
MaxVersion: authconfig.DefaultTLSMaxVersion,
|
||||
Renegotiation: authconfig.DefaultTLSRenegotiation,
|
||||
CipherSuites: authconfig.DefaultTLSCipherSuites,
|
||||
},
|
||||
Templates: p.getTemplates(),
|
||||
}
|
||||
if p.enableSSH {
|
||||
enableSSHCA := true
|
||||
config.SSH = &authority.SSHConfig{
|
||||
config.SSH = &authconfig.SSHConfig{
|
||||
HostKey: p.sshHostKey,
|
||||
UserKey: p.sshUserKey,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue