Merge branch 'master' into hs/acme-revocation

This commit is contained in:
Herman Slatman 2021-07-09 11:22:25 +02:00
commit 8f7e700f09
No known key found for this signature in database
GPG key ID: F4D8A44EA0A75A4F
83 changed files with 8009 additions and 907 deletions

View file

@ -8,6 +8,8 @@ linters-settings:
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - (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).Warnf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
revive:
min-confidence: 0
gocyclo: gocyclo:
min-complexity: 10 min-complexity: 10
maligned: maligned:

View file

@ -29,7 +29,7 @@ ci: testcgo build
bootstra%: bootstra%:
# Using a released version of golangci-lint to take into account custom replacements in their go.mod # 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% .PHONY: bootstra%

View file

@ -574,13 +574,13 @@ func TestHandler_GetChallenge(t *testing.T) {
assert.Equals(t, azID, "authzID") assert.Equals(t, azID, "authzID")
return &acme.Challenge{ return &acme.Challenge{
Status: acme.StatusPending, Status: acme.StatusPending,
Type: "http-01", Type: acme.HTTP01,
AccountID: "accID", AccountID: "accID",
}, nil }, nil
}, },
MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error { MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
assert.Equals(t, ch.Status, acme.StatusPending) 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.AccountID, "accID")
assert.Equals(t, ch.AuthorizationID, "authzID") assert.Equals(t, ch.AuthorizationID, "authzID")
assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String()) assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String())
@ -616,13 +616,13 @@ func TestHandler_GetChallenge(t *testing.T) {
return &acme.Challenge{ return &acme.Challenge{
ID: "chID", ID: "chID",
Status: acme.StatusPending, Status: acme.StatusPending,
Type: "http-01", Type: acme.HTTP01,
AccountID: "accID", AccountID: "accID",
}, nil }, nil
}, },
MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error { MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
assert.Equals(t, ch.Status, acme.StatusPending) 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.AccountID, "accID")
assert.Equals(t, ch.AuthorizationID, "authzID") assert.Equals(t, ch.AuthorizationID, "authzID")
assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String()) assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String())
@ -633,7 +633,7 @@ func TestHandler_GetChallenge(t *testing.T) {
ID: "chID", ID: "chID",
Status: acme.StatusPending, Status: acme.StatusPending,
AuthorizationID: "authzID", AuthorizationID: "authzID",
Type: "http-01", Type: acme.HTTP01,
AccountID: "accID", AccountID: "accID",
URL: url, URL: url,
Error: acme.NewError(acme.ErrorConnectionType, "force"), Error: acme.NewError(acme.ErrorConnectionType, "force"),

View file

@ -288,13 +288,13 @@ func (h *Handler) lookupProvisioner(next nextHTTP) nextHTTP {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
name := chi.URLParam(r, "provisionerID") nameEscaped := chi.URLParam(r, "provisionerID")
provID, err := url.PathUnescape(name) name, err := url.PathUnescape(nameEscaped)
if err != nil { 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 return
} }
p, err := h.ca.LoadProvisionerByID("acme/" + provID) p, err := h.ca.LoadProvisionerByName(name)
if err != nil { if err != nil {
api.WriteError(w, err) api.WriteError(w, err)
return return

View file

@ -5,6 +5,7 @@ import (
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"net"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -28,9 +29,12 @@ func (n *NewOrderRequest) Validate() error {
return acme.NewError(acme.ErrorMalformedType, "identifiers list cannot be empty") return acme.NewError(acme.ErrorMalformedType, "identifiers list cannot be empty")
} }
for _, id := range n.Identifiers { 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) 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 return nil
} }
@ -85,6 +89,7 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) {
"failed to unmarshal new-order request payload")) "failed to unmarshal new-order request payload"))
return return
} }
if err := nor.Validate(); err != nil { if err := nor.Validate(); err != nil {
api.WriteError(w, err) api.WriteError(w, err)
return return
@ -149,15 +154,9 @@ func (h *Handler) newAuthorization(ctx context.Context, az *acme.Authorization)
} }
} }
var ( chTypes := challengeTypes(az)
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"}...)
}
var err error
az.Token, err = randutil.Alphanumeric(32) az.Token, err = randutil.Alphanumeric(32)
if err != nil { if err != nil {
return acme.WrapErrorISE(err, "error generating random alphanumeric ID") 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)) w.Header().Set("Location", h.linker.GetLink(ctx, OrderLinkType, o.ID))
api.JSON(w, o) 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
}

View file

@ -10,6 +10,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"reflect"
"testing" "testing"
"time" "time"
@ -44,6 +45,22 @@ func TestNewOrderRequest_Validate(t *testing.T) {
err: acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: foo"), 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 { "ok": func(t *testing.T) test {
nbf := time.Now().UTC().Add(time.Minute) nbf := time.Now().UTC().Add(time.Minute)
naf := time.Now().UTC().Add(5 * time.Minute) naf := time.Now().UTC().Add(5 * time.Minute)
@ -60,6 +77,68 @@ func TestNewOrderRequest_Validate(t *testing.T) {
naf: naf, 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 { for name, run := range tests {
tc := run(t) tc := run(t)
@ -395,7 +474,7 @@ func TestHandler_newAuthorization(t *testing.T) {
db: &acme.MockDB{ db: &acme.MockDB{
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
assert.Equals(t, ch.AccountID, az.AccountID) 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.Token, az.Token)
assert.Equals(t, ch.Status, acme.StatusPending) assert.Equals(t, ch.Status, acme.StatusPending)
assert.Equals(t, ch.Value, az.Identifier.Value) assert.Equals(t, ch.Value, az.Identifier.Value)
@ -424,15 +503,15 @@ func TestHandler_newAuthorization(t *testing.T) {
switch count { switch count {
case 0: case 0:
ch.ID = "dns" ch.ID = "dns"
assert.Equals(t, ch.Type, "dns-01") assert.Equals(t, ch.Type, acme.DNS01)
ch1 = &ch ch1 = &ch
case 1: case 1:
ch.ID = "http" ch.ID = "http"
assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.Type, acme.HTTP01)
ch2 = &ch ch2 = &ch
case 2: case 2:
ch.ID = "tls" ch.ID = "tls"
assert.Equals(t, ch.Type, "tls-alpn-01") assert.Equals(t, ch.Type, acme.TLSALPN01)
ch3 = &ch ch3 = &ch
default: default:
assert.FatalError(t, errors.New("test logic error")) assert.FatalError(t, errors.New("test logic error"))
@ -478,15 +557,15 @@ func TestHandler_newAuthorization(t *testing.T) {
switch count { switch count {
case 0: case 0:
ch.ID = "dns" ch.ID = "dns"
assert.Equals(t, ch.Type, "dns-01") assert.Equals(t, ch.Type, acme.DNS01)
ch1 = &ch ch1 = &ch
case 1: case 1:
ch.ID = "http" ch.ID = "http"
assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.Type, acme.HTTP01)
ch2 = &ch ch2 = &ch
case 2: case 2:
ch.ID = "tls" ch.ID = "tls"
assert.Equals(t, ch.Type, "tls-alpn-01") assert.Equals(t, ch.Type, acme.TLSALPN01)
ch3 = &ch ch3 = &ch
default: default:
assert.FatalError(t, errors.New("test logic error")) assert.FatalError(t, errors.New("test logic error"))
@ -528,7 +607,7 @@ func TestHandler_newAuthorization(t *testing.T) {
db: &acme.MockDB{ db: &acme.MockDB{
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
ch.ID = "dns" 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.AccountID, az.AccountID)
assert.Equals(t, ch.Token, az.Token) assert.Equals(t, ch.Token, az.Token)
assert.Equals(t, ch.Status, acme.StatusPending) assert.Equals(t, ch.Status, acme.StatusPending)
@ -695,7 +774,7 @@ func TestHandler_NewOrder(t *testing.T) {
db: &acme.MockDB{ db: &acme.MockDB{
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error { MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
assert.Equals(t, ch.AccountID, "accID") 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.NotEquals(t, ch.Token, "")
assert.Equals(t, ch.Status, acme.StatusPending) assert.Equals(t, ch.Status, acme.StatusPending)
assert.Equals(t, ch.Value, "zap.internal") assert.Equals(t, ch.Value, "zap.internal")
@ -730,15 +809,15 @@ func TestHandler_NewOrder(t *testing.T) {
switch count { switch count {
case 0: case 0:
ch.ID = "dns" ch.ID = "dns"
assert.Equals(t, ch.Type, "dns-01") assert.Equals(t, ch.Type, acme.DNS01)
ch1 = &ch ch1 = &ch
case 1: case 1:
ch.ID = "http" ch.ID = "http"
assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.Type, acme.HTTP01)
ch2 = &ch ch2 = &ch
case 2: case 2:
ch.ID = "tls" ch.ID = "tls"
assert.Equals(t, ch.Type, "tls-alpn-01") assert.Equals(t, ch.Type, acme.TLSALPN01)
ch3 = &ch ch3 = &ch
default: default:
assert.FatalError(t, errors.New("test logic error")) assert.FatalError(t, errors.New("test logic error"))
@ -802,22 +881,22 @@ func TestHandler_NewOrder(t *testing.T) {
switch chCount { switch chCount {
case 0: case 0:
ch.ID = "dns" ch.ID = "dns"
assert.Equals(t, ch.Type, "dns-01") assert.Equals(t, ch.Type, acme.DNS01)
assert.Equals(t, ch.Value, "zap.internal") assert.Equals(t, ch.Value, "zap.internal")
ch1 = &ch ch1 = &ch
case 1: case 1:
ch.ID = "http" ch.ID = "http"
assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.Type, acme.HTTP01)
assert.Equals(t, ch.Value, "zap.internal") assert.Equals(t, ch.Value, "zap.internal")
ch2 = &ch ch2 = &ch
case 2: case 2:
ch.ID = "tls" 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") assert.Equals(t, ch.Value, "zap.internal")
ch3 = &ch ch3 = &ch
case 3: case 3:
ch.ID = "dns" ch.ID = "dns"
assert.Equals(t, ch.Type, "dns-01") assert.Equals(t, ch.Type, acme.DNS01)
assert.Equals(t, ch.Value, "zar.internal") assert.Equals(t, ch.Value, "zar.internal")
ch4 = &ch ch4 = &ch
default: default:
@ -842,7 +921,7 @@ func TestHandler_NewOrder(t *testing.T) {
az.ID = "az2ID" az.ID = "az2ID"
az2ID = &az.ID az2ID = &az.ID
assert.Equals(t, az.Identifier, acme.Identifier{ assert.Equals(t, az.Identifier, acme.Identifier{
Type: "dns", Type: acme.DNS,
Value: "zar.internal", Value: "zar.internal",
}) })
assert.Equals(t, az.Wildcard, true) assert.Equals(t, az.Wildcard, true)
@ -917,15 +996,15 @@ func TestHandler_NewOrder(t *testing.T) {
switch count { switch count {
case 0: case 0:
ch.ID = "dns" ch.ID = "dns"
assert.Equals(t, ch.Type, "dns-01") assert.Equals(t, ch.Type, acme.DNS01)
ch1 = &ch ch1 = &ch
case 1: case 1:
ch.ID = "http" ch.ID = "http"
assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.Type, acme.HTTP01)
ch2 = &ch ch2 = &ch
case 2: case 2:
ch.ID = "tls" ch.ID = "tls"
assert.Equals(t, ch.Type, "tls-alpn-01") assert.Equals(t, ch.Type, acme.TLSALPN01)
ch3 = &ch ch3 = &ch
default: default:
assert.FatalError(t, errors.New("test logic error")) assert.FatalError(t, errors.New("test logic error"))
@ -1009,15 +1088,15 @@ func TestHandler_NewOrder(t *testing.T) {
switch count { switch count {
case 0: case 0:
ch.ID = "dns" ch.ID = "dns"
assert.Equals(t, ch.Type, "dns-01") assert.Equals(t, ch.Type, acme.DNS01)
ch1 = &ch ch1 = &ch
case 1: case 1:
ch.ID = "http" ch.ID = "http"
assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.Type, acme.HTTP01)
ch2 = &ch ch2 = &ch
case 2: case 2:
ch.ID = "tls" ch.ID = "tls"
assert.Equals(t, ch.Type, "tls-alpn-01") assert.Equals(t, ch.Type, acme.TLSALPN01)
ch3 = &ch ch3 = &ch
default: default:
assert.FatalError(t, errors.New("test logic error")) assert.FatalError(t, errors.New("test logic error"))
@ -1100,15 +1179,15 @@ func TestHandler_NewOrder(t *testing.T) {
switch count { switch count {
case 0: case 0:
ch.ID = "dns" ch.ID = "dns"
assert.Equals(t, ch.Type, "dns-01") assert.Equals(t, ch.Type, acme.DNS01)
ch1 = &ch ch1 = &ch
case 1: case 1:
ch.ID = "http" ch.ID = "http"
assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.Type, acme.HTTP01)
ch2 = &ch ch2 = &ch
case 2: case 2:
ch.ID = "tls" ch.ID = "tls"
assert.Equals(t, ch.Type, "tls-alpn-01") assert.Equals(t, ch.Type, acme.TLSALPN01)
ch3 = &ch ch3 = &ch
default: default:
assert.FatalError(t, errors.New("test logic error")) assert.FatalError(t, errors.New("test logic error"))
@ -1192,15 +1271,15 @@ func TestHandler_NewOrder(t *testing.T) {
switch count { switch count {
case 0: case 0:
ch.ID = "dns" ch.ID = "dns"
assert.Equals(t, ch.Type, "dns-01") assert.Equals(t, ch.Type, acme.DNS01)
ch1 = &ch ch1 = &ch
case 1: case 1:
ch.ID = "http" ch.ID = "http"
assert.Equals(t, ch.Type, "http-01") assert.Equals(t, ch.Type, acme.HTTP01)
ch2 = &ch ch2 = &ch
case 2: case 2:
ch.ID = "tls" ch.ID = "tls"
assert.Equals(t, ch.Type, "tls-alpn-01") assert.Equals(t, ch.Type, acme.TLSALPN01)
ch3 = &ch ch3 = &ch
default: default:
assert.FatalError(t, errors.New("test logic error")) 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)
}
})
}
}

View file

@ -21,18 +21,26 @@ import (
"go.step.sm/crypto/jose" "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. // Challenge represents an ACME response Challenge type.
type Challenge struct { type Challenge struct {
ID string `json:"-"` ID string `json:"-"`
AccountID string `json:"-"` AccountID string `json:"-"`
AuthorizationID string `json:"-"` AuthorizationID string `json:"-"`
Value string `json:"-"` Value string `json:"-"`
Type string `json:"type"` Type ChallengeType `json:"type"`
Status Status `json:"status"` Status Status `json:"status"`
Token string `json:"token"` Token string `json:"token"`
ValidatedAt string `json:"validated,omitempty"` ValidatedAt string `json:"validated,omitempty"`
URL string `json:"url"` URL string `json:"url"`
Error *Error `json:"error,omitempty"` Error *Error `json:"error,omitempty"`
} }
// ToLog enables response logging. // ToLog enables response logging.
@ -54,11 +62,11 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey,
return nil return nil
} }
switch ch.Type { switch ch.Type {
case "http-01": case HTTP01:
return http01Validate(ctx, ch, db, jwk, vo) return http01Validate(ctx, ch, db, jwk, vo)
case "dns-01": case DNS01:
return dns01Validate(ctx, ch, db, jwk, vo) return dns01Validate(ctx, ch, db, jwk, vo)
case "tls-alpn-01": case TLSALPN01:
return tlsalpn01Validate(ctx, ch, db, jwk, vo) return tlsalpn01Validate(ctx, ch, db, jwk, vo)
default: default:
return NewErrorISE("unexpected challenge type '%s'", ch.Type) 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 // ACME servers that implement "acme-tls/1" MUST only negotiate TLS 1.2
// [RFC5246] or higher when connecting to clients for validation. // [RFC5246] or higher when connecting to clients for validation.
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
ServerName: ch.Value, ServerName: serverName(ch),
InsecureSkipVerify: true, // we expect a self-signed challenge certificate 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] leafCert := certs[0]
if len(leafCert.DNSNames) != 1 || !strings.EqualFold(leafCert.DNSNames[0], ch.Value) { // if no DNS names present, look for IP address and verify that exactly one exists
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, if len(leafCert.DNSNames) == 0 {
"incorrect certificate for tls-alpn-01 challenge: leaf certificate must contain a single DNS name, %v", ch.Value)) 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 IP address or DNS name, %v", ch.Value))
}
} }
idPeAcmeIdentifier := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31} 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 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 // KeyAuthorization creates the ACME key authorization value from a token
// and a jwk. // and a jwk.
func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) { func KeyAuthorization(token string, jwk *jose.JSONWebKey) (string, error) {

View file

@ -1544,7 +1544,7 @@ func TestTLSALPN01Validate(t *testing.T) {
err: NewErrorISE("failure saving error to acme challenge: force"), 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() ch := makeTLSCh()
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) 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.Type, ch.Type)
assert.Equals(t, updch.Value, ch.Value) 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.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
assert.Equals(t, updch.Error.Type, err.Type) 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.Type, ch.Type)
assert.Equals(t, updch.Value, ch.Value) 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.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
assert.Equals(t, updch.Error.Type, err.Type) 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.Type, ch.Type)
assert.Equals(t, updch.Value, ch.Value) 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.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
assert.Equals(t, updch.Error.Type, err.Type) 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.Type, ch.Type)
assert.Equals(t, updch.Value, ch.Value) 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.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
assert.Equals(t, updch.Error.Type, err.Type) assert.Equals(t, updch.Error.Type, err.Type)
@ -2187,6 +2187,43 @@ func TestTLSALPN01Validate(t *testing.T) {
srv, tlsDial := newTestTLSALPNServer(cert) srv, tlsDial := newTestTLSALPNServer(cert)
srv.Start() 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{ return test{
ch: ch, ch: ch,
vo: &ValidateChallengeOptions{ 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)
}
})
}
}

View file

@ -13,7 +13,7 @@ import (
type CertificateAuthority interface { type CertificateAuthority interface {
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
Revoke(context.Context, *authority.RevokeOptions) 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. // Clock that returns time in UTC rounded to seconds.

View file

@ -11,15 +11,15 @@ import (
) )
type dbChallenge struct { type dbChallenge struct {
ID string `json:"id"` ID string `json:"id"`
AccountID string `json:"accountID"` AccountID string `json:"accountID"`
Type string `json:"type"` Type acme.ChallengeType `json:"type"`
Status acme.Status `json:"status"` Status acme.Status `json:"status"`
Token string `json:"token"` Token string `json:"token"`
Value string `json:"value"` Value string `json:"value"`
ValidatedAt string `json:"validatedAt"` ValidatedAt string `json:"validatedAt"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Error *acme.Error `json:"error"` Error *acme.Error `json:"error"`
} }
func (dbc *dbChallenge) clone() *dbChallenge { func (dbc *dbChallenge) clone() *dbChallenge {

View file

@ -1,9 +1,11 @@
package acme package acme
import ( import (
"bytes"
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"net"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -12,10 +14,17 @@ import (
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
) )
type IdentifierType string
const (
IP IdentifierType = "ip"
DNS IdentifierType = "dns"
)
// Identifier encodes the type that an order pertains to. // Identifier encodes the type that an order pertains to.
type Identifier struct { type Identifier struct {
Type string `json:"type"` Type IdentifierType `json:"type"`
Value string `json:"value"` Value string `json:"value"`
} }
// Order contains order metadata for the ACME protocol order type. // Order contains order metadata for the ACME protocol order type.
@ -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) return NewErrorISE("unexpected status %s for order %s", o.Status, o.ID)
} }
// RFC8555: The CSR MUST indicate the exact same set of requested // canonicalize the CSR to allow for comparison
// identifiers as the initial newOrder request. Identifiers of type "dns" csr = canonicalize(csr)
// 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)
// Validate identifier names against CSR alternative names. // retrieve the requested SANs for the Order
// sans, err := o.sans(csr)
// Note that with certificate templates we are not going to check for the if err != nil {
// absence of other SANs as they will only be set if the templates allows return err
// 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],
}
} }
// Get authorizations from the ACME provisioner. // Get authorizations from the ACME provisioner.
@ -213,6 +194,122 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
return nil 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 // 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 // of them are lowercased. The returned names will be in their lowercased form
// and sorted alphabetically. // and sorted alphabetically.
@ -228,3 +325,23 @@ func uniqueSortedLowerNames(names []string) (unique []string) {
sort.Strings(unique) sort.Strings(unique)
return 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
}

View file

@ -5,6 +5,8 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/json" "encoding/json"
"net"
"reflect"
"testing" "testing"
"time" "time"
@ -12,6 +14,7 @@ import (
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/x509util"
) )
func TestOrder_UpdateStatus(t *testing.T) { func TestOrder_UpdateStatus(t *testing.T) {
@ -262,10 +265,10 @@ func TestOrder_UpdateStatus(t *testing.T) {
} }
type mockSignAuth struct { type mockSignAuth struct {
sign func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) 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{} ret1, ret2 interface{}
err error err error
} }
func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { func (m *mockSignAuth) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, 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 return []*x509.Certificate{m.ret1.(*x509.Certificate), m.ret2.(*x509.Certificate)}, m.err
} }
func (m *mockSignAuth) LoadProvisionerByID(id string) (provisioner.Interface, error) { func (m *mockSignAuth) LoadProvisionerByName(name string) (provisioner.Interface, error) {
if m.loadProvisionerByID != nil { if m.loadProvisionerByName != nil {
return m.loadProvisionerByID(id) return m.loadProvisionerByName(name)
} }
return m.ret1.(provisioner.Interface), m.err 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), 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 { "fail/error-provisioner-auth": func(t *testing.T) test {
now := clock.Now() now := clock.Now()
o := &Order{ o := &Order{
@ -655,7 +603,7 @@ func TestOrder_Finalize(t *testing.T) {
err: NewErrorISE("error updating order oID: force"), 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() now := clock.Now()
o := &Order{ o := &Order{
ID: "oID", ID: "oID",
@ -679,6 +627,131 @@ func TestOrder_Finalize(t *testing.T) {
bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}} bar := &x509.Certificate{Subject: pkix.Name{CommonName: "bar"}}
baz := &x509.Certificate{Subject: pkix.Name{CommonName: "baz"}} 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{ return test{
o: o, o: o,
csr: csr, 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)
}
})
}
}

View file

@ -21,6 +21,7 @@ import (
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
@ -32,13 +33,13 @@ type Authority interface {
// context specifies the Authorize[Sign|Revoke|etc.] method. // context specifies the Authorize[Sign|Revoke|etc.] method.
Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error) Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error)
AuthorizeSign(ott string) ([]provisioner.SignOption, error) AuthorizeSign(ott string) ([]provisioner.SignOption, error)
GetTLSOptions() *authority.TLSOptions GetTLSOptions() *config.TLSOptions
Root(shasum string) (*x509.Certificate, error) Root(shasum string) (*x509.Certificate, error)
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
Renew(peer *x509.Certificate) ([]*x509.Certificate, error) Renew(peer *x509.Certificate) ([]*x509.Certificate, error)
Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) Rekey(peer *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, 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) GetProvisioners(cursor string, limit int) (provisioner.List, string, error)
Revoke(context.Context, *authority.RevokeOptions) error Revoke(context.Context, *authority.RevokeOptions) error
GetEncryptedKey(kid string) (string, 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. // Provisioners returns the list of provisioners configured in the authority.
func (h *caHandler) Provisioners(w http.ResponseWriter, r *http.Request) { func (h *caHandler) Provisioners(w http.ResponseWriter, r *http.Request) {
cursor, limit, err := parseCursor(r) cursor, limit, err := ParseCursor(r)
if err != nil { if err != nil {
WriteError(w, errs.BadRequestErr(err)) WriteError(w, errs.BadRequestErr(err))
return 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() q := r.URL.Query()
cursor = q.Get("cursor") cursor = q.Get("cursor")
if v := q.Get("limit"); len(v) > 0 { if v := q.Get("limit"); len(v) > 0 {

View file

@ -430,6 +430,7 @@ type mockProvisioner struct {
ret1, ret2, ret3 interface{} ret1, ret2, ret3 interface{}
err error err error
getID func() string getID func() string
getIDForToken func() string
getTokenID func(string) (string, error) getTokenID func(string) (string, error)
getName func() string getName func() string
getType func() provisioner.Type getType func() provisioner.Type
@ -452,6 +453,13 @@ func (m *mockProvisioner) GetID() string {
return m.ret1.(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) { func (m *mockProvisioner) GetTokenID(token string) (string, error) {
if m.getTokenID != nil { if m.getTokenID != nil {
return m.getTokenID(token) return m.getTokenID(token)
@ -553,7 +561,7 @@ type mockAuthority struct {
renew func(cert *x509.Certificate) ([]*x509.Certificate, error) renew func(cert *x509.Certificate) ([]*x509.Certificate, error)
rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) rekey func(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error)
loadProvisionerByCertificate func(cert *x509.Certificate) (provisioner.Interface, 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) getProvisioners func(nextCursor string, limit int) (provisioner.List, string, error)
revoke func(context.Context, *authority.RevokeOptions) error revoke func(context.Context, *authority.RevokeOptions) error
getEncryptedKey func(kid string) (string, 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 return m.ret1.(provisioner.Interface), m.err
} }
func (m *mockAuthority) LoadProvisionerByID(provID string) (provisioner.Interface, error) { func (m *mockAuthority) LoadProvisionerByName(name string) (provisioner.Interface, error) {
if m.loadProvisionerByID != nil { if m.loadProvisionerByName != nil {
return m.loadProvisionerByID(provID) return m.loadProvisionerByName(name)
} }
return m.ret1.(provisioner.Interface), m.err return m.ret1.(provisioner.Interface), m.err
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/authority/admin"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/scep" "github.com/smallstep/certificates/scep"
@ -19,6 +20,9 @@ func WriteError(w http.ResponseWriter, err error) {
case *acme.Error: case *acme.Error:
acme.WriteError(w, k) acme.WriteError(w, k)
return return
case *admin.Error:
admin.WriteError(w, k)
return
case *scep.Error: case *scep.Error:
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
default: default:

View file

@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
) )
@ -37,11 +37,11 @@ func (s *SignRequest) Validate() error {
// SignResponse is the response object of the certificate signature request. // SignResponse is the response object of the certificate signature request.
type SignResponse struct { type SignResponse struct {
ServerPEM Certificate `json:"crt"` ServerPEM Certificate `json:"crt"`
CaPEM Certificate `json:"ca"` CaPEM Certificate `json:"ca"`
CertChainPEM []Certificate `json:"certChain"` CertChainPEM []Certificate `json:"certChain"`
TLSOptions *authority.TLSOptions `json:"tlsOptions,omitempty"` TLSOptions *config.TLSOptions `json:"tlsOptions,omitempty"`
TLS *tls.ConnectionState `json:"-"` TLS *tls.ConnectionState `json:"-"`
} }
// Sign is an HTTP handler that reads a certificate request and an // Sign is an HTTP handler that reads a certificate request and an

View file

@ -10,6 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
@ -22,12 +23,12 @@ type SSHAuthority interface {
RenewSSH(ctx context.Context, cert *ssh.Certificate) (*ssh.Certificate, error) 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) 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) SignSSHAddUser(ctx context.Context, key ssh.PublicKey, cert *ssh.Certificate) (*ssh.Certificate, error)
GetSSHRoots(ctx context.Context) (*authority.SSHKeys, error) GetSSHRoots(ctx context.Context) (*config.SSHKeys, error)
GetSSHFederation(ctx context.Context) (*authority.SSHKeys, error) GetSSHFederation(ctx context.Context) (*config.SSHKeys, error)
GetSSHConfig(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error) GetSSHConfig(ctx context.Context, typ string, data map[string]string) ([]templates.Output, error)
CheckSSHHost(ctx context.Context, principal string, token string) (bool, error) CheckSSHHost(ctx context.Context, principal string, token string) (bool, error)
GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]authority.Host, error) GetSSHHosts(ctx context.Context, cert *x509.Certificate) ([]config.Host, error)
GetSSHBastion(ctx context.Context, user string, hostname string) (*authority.Bastion, error) GetSSHBastion(ctx context.Context, user string, hostname string) (*config.Bastion, error)
} }
// SSHSignRequest is the request body of an SSH certificate request. // 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 // SSHGetHostsResponse is the response object that returns the list of valid
// hosts for SSH. // hosts for SSH.
type SSHGetHostsResponse struct { type SSHGetHostsResponse struct {
Hosts []authority.Host `json:"hosts"` Hosts []config.Host `json:"hosts"`
} }
// MarshalJSON implements the json.Marshaler interface. Returns a quoted, // MarshalJSON implements the json.Marshaler interface. Returns a quoted,
@ -239,8 +240,8 @@ func (r *SSHBastionRequest) Validate() error {
// SSHBastionResponse is the response body used to return the bastion for a // SSHBastionResponse is the response body used to return the bastion for a
// given host. // given host.
type SSHBastionResponse struct { type SSHBastionResponse struct {
Hostname string `json:"hostname"` 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 // SSHSign is an HTTP handler that reads an SignSSHRequest with a one-time-token

View file

@ -3,11 +3,14 @@ package api
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/logging" "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. // 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) 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 // ReadJSON reads JSON from the request body and stores it in the value
// pointed by v. // pointed by v.
func ReadJSON(r io.Reader, v interface{}) error { func ReadJSON(r io.Reader, v interface{}) error {
@ -72,3 +98,13 @@ func ReadJSON(r io.Reader, v interface{}) error {
} }
return nil 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)
}

View 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)
}

View 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))
}

View 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")
)

View 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
View 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
}

View 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)
}

File diff suppressed because it is too large Load diff

View 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)

View 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)
}

File diff suppressed because it is too large Load diff

223
authority/admin/errors.go Normal file
View 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)
}
}

View 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
View 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
}

View file

@ -12,8 +12,13 @@ import (
"github.com/smallstep/certificates/cas" "github.com/smallstep/certificates/cas"
"github.com/smallstep/certificates/scep" "github.com/smallstep/certificates/scep"
"go.step.sm/linkedca"
"github.com/pkg/errors" "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" "github.com/smallstep/certificates/authority/provisioner"
casapi "github.com/smallstep/certificates/cas/apiv1" casapi "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
@ -21,25 +26,25 @@ import (
kmsapi "github.com/smallstep/certificates/kms/apiv1" kmsapi "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/kms/sshagentkms" "github.com/smallstep/certificates/kms/sshagentkms"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"github.com/smallstep/nosql"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
const (
legacyAuthority = "step-certificate-authority"
)
// Authority implements the Certificate Authority internal interface. // Authority implements the Certificate Authority internal interface.
type Authority struct { type Authority struct {
config *Config config *config.Config
keyManager kms.KeyManager keyManager kms.KeyManager
provisioners *provisioner.Collection provisioners *provisioner.Collection
admins *administrator.Collection
db db.AuthDB db db.AuthDB
adminDB admin.DB
templates *templates.Templates templates *templates.Templates
// X509 CA // X509 CA
x509CAService cas.CertificateAuthorityService x509CAService cas.CertificateAuthorityService
rootX509Certs []*x509.Certificate rootX509Certs []*x509.Certificate
rootX509CertPool *x509.CertPool
federatedX509Certs []*x509.Certificate federatedX509Certs []*x509.Certificate
certificates *sync.Map certificates *sync.Map
@ -59,14 +64,16 @@ type Authority struct {
startTime time.Time startTime time.Time
// Custom functions // 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) 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 getIdentityFunc provisioner.GetIdentityFunc
adminMutex sync.RWMutex
} }
// New creates and initiates a new Authority type. // 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() err := config.Validate()
if err != nil { if err != nil {
return nil, err return nil, err
@ -96,7 +103,7 @@ func New(config *Config, opts ...Option) (*Authority, error) {
// project without the limitations of the config. // project without the limitations of the config.
func NewEmbedded(opts ...Option) (*Authority, error) { func NewEmbedded(opts ...Option) (*Authority, error) {
a := &Authority{ a := &Authority{
config: &Config{}, config: &config.Config{},
certificates: new(sync.Map), certificates: new(sync.Map),
} }
@ -120,7 +127,7 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
} }
// Initialize config required fields. // Initialize config required fields.
a.config.init() a.config.Init()
// Initialize authority from options or configuration. // Initialize authority from options or configuration.
if err := a.init(); err != nil { if err := a.init(); err != nil {
@ -130,6 +137,65 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
return a, nil 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. // init performs validation and initializes the fields of an Authority struct.
func (a *Authority) init() error { func (a *Authority) init() error {
// Check if handler has already been validated/initialized. // 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.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. // Read federated certificates and store them in the certificates map.
if len(a.federatedX509Certs) == 0 { if len(a.federatedX509Certs) == 0 {
a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots)) a.federatedX509Certs = make([]*x509.Certificate, len(a.config.FederatedRoots))
@ -317,30 +388,11 @@ func (a *Authority) init() error {
tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, a.sshCAUserFederatedCerts[1:]...) tmplVars.SSH.UserFederatedKeys = append(tmplVars.SSH.UserFederatedKeys, a.sshCAUserFederatedCerts[1:]...)
} }
// Merge global and configuration claims // Check if a KMS with decryption capability is required and available
claimer, err := provisioner.NewClaimer(a.config.AuthorityConfig.Claims, globalProvisionerClaims) if a.requiresDecrypter() {
if err != nil { if _, ok := a.keyManager.(kmsapi.Decrypter); !ok {
return err 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 // 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? // TODO: mimick the x509CAService GetCertificateAuthority here too?
} }
// Store all the provisioners if a.config.AuthorityConfig.EnableAdmin {
for _, p := range a.config.AuthorityConfig.Provisioners { // Initialize step-ca Admin Database if it's not already initialized using
if err := p.Init(config); err != nil { // WithAdminDB.
return err 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. // Configure templates, currently only ssh templates are supported.
@ -423,6 +503,17 @@ func (a *Authority) GetDatabase() db.AuthDB {
return a.db 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. // Shutdown safely shuts down any clients, databases, etc. held by the Authority.
func (a *Authority) Shutdown() error { func (a *Authority) Shutdown() error {
if err := a.keyManager.Close(); err != nil { if err := a.keyManager.Close(); err != nil {

View file

@ -454,8 +454,6 @@ func TestAuthority_GetSCEPService(t *testing.T) {
// getIdentityFunc: tt.fields.getIdentityFunc, // getIdentityFunc: tt.fields.getIdentityFunc,
// } // }
a, err := New(tt.fields.config) a, err := New(tt.fields.config)
fmt.Println(err)
fmt.Println(a)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("Authority.New(), error = %v, wantErr %v", err, tt.wantErr) t.Errorf("Authority.New(), error = %v, wantErr %v", err, tt.wantErr)
return return

View file

@ -7,10 +7,13 @@ import (
"encoding/hex" "encoding/hex"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/smallstep/certificates/authority/admin"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/linkedca"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -73,25 +76,121 @@ func (a *Authority) authorizeToken(ctx context.Context, token string) (provision
// Store the token to protect against reuse unless it's skipped. // 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 we cannot get a token id from the provisioner, just hash the token.
if !SkipTokenReuseFromContext(ctx) { if !SkipTokenReuseFromContext(ctx) {
if reuseKey, err := p.GetTokenID(token); err == nil { if err = a.UseToken(token, p); err != nil {
if reuseKey == "" { return nil, err
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,
"authority.authorizeToken: failed when attempting to store token")
}
if !ok {
return nil, errs.Unauthorized("authority.authorizeToken: token already used")
}
} }
} }
return p, nil 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 errs.Wrap(http.StatusInternalServerError, err,
"authority.authorizeToken: failed when attempting to store token")
}
if !ok {
return errs.Unauthorized("authority.authorizeToken: token already used")
}
}
return nil
}
// Authorize grabs the method from the context and authorizes the request by // Authorize grabs the method from the context and authorizes the request by
// validating the one-time-token. // validating the one-time-token.
func (a *Authority) Authorize(ctx context.Context, token string) ([]provisioner.SignOption, error) { func (a *Authority) Authorize(ctx context.Context, token string) ([]provisioner.SignOption, error) {

View file

@ -822,7 +822,7 @@ func TestAuthority_authorizeRenew(t *testing.T) {
return &authorizeTest{ return &authorizeTest{
auth: a, auth: a,
cert: renewDisabledCrt, 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, code: http.StatusUnauthorized,
} }
}, },

View file

@ -1,298 +1,46 @@
package authority package authority
import ( import "github.com/smallstep/certificates/authority/config"
"encoding/json"
"fmt"
"net"
"os"
"time"
"github.com/pkg/errors" // Config is an alias to support older APIs.
"github.com/smallstep/certificates/authority/provisioner" type Config = config.Config
cas "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db"
kms "github.com/smallstep/certificates/kms/apiv1"
"github.com/smallstep/certificates/templates"
)
var ( // LoadConfiguration is an alias to support older APIs.
// DefaultTLSOptions represents the default TLS version as well as the cipher var LoadConfiguration = config.LoadConfiguration
// 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,
}
)
// Config represents the CA configuration and it's mapped to a JSON object. // AuthConfig is an alias to support older APIs.
type Config struct { type AuthConfig = config.AuthConfig
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 // TLS
// 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"`
}
// AuthConfig represents the configuration options for the authority. An // ASN1DN is an alias to support older APIs.
// underlaying registration authority can also be configured using the type ASN1DN = config.ASN1DN
// 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"`
}
// init initializes the required fields in the AuthConfig if they are not // DefaultTLSOptions is an alias to support older APIs.
// provided. var DefaultTLSOptions = config.DefaultTLSOptions
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. // TLSOptions is an alias to support older APIs.
func (c *AuthConfig) Validate(audiences provisioner.Audiences) error { type TLSOptions = config.TLSOptions
if c == nil {
return errors.New("authority cannot be undefined")
}
// Initialize required fields. // CipherSuites is an alias to support older APIs.
c.init() type CipherSuites = config.CipherSuites
// Check that only one K8sSA is enabled // SSH
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 { // SSHConfig is an alias to support older APIs.
return errors.New("authority.backdate cannot be less than 0") 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 // HostTag is an alias to support older APIs.
// configuration struct. type HostTag = config.HostTag
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 // Host is an alias to support older APIs.
if err := json.NewDecoder(f).Decode(&c); err != nil { type Host = config.Host
return nil, errors.Wrapf(err, "error parsing %s", filename)
}
c.init() // SSHPublicKey is an alias to support older APIs.
type SSHPublicKey = config.SSHPublicKey
return &c, nil // SSHKeys is an alias to support older APIs.
} type SSHKeys = config.SSHKeys
// 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
}

294
authority/config/config.go Normal file
View 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
}

View file

@ -1,4 +1,4 @@
package authority package config
import ( import (
"fmt" "fmt"
@ -8,12 +8,14 @@ import (
"github.com/smallstep/assert" "github.com/smallstep/assert"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
_ "github.com/smallstep/certificates/cas"
) )
func TestConfigValidate(t *testing.T) { 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) 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) assert.FatalError(t, err)
ac := &AuthConfig{ ac := &AuthConfig{
Provisioners: provisioner.List{ Provisioners: provisioner.List{
@ -39,9 +41,9 @@ func TestConfigValidate(t *testing.T) {
"empty-address": func(t *testing.T) ConfigValidateTest { "empty-address": func(t *testing.T) ConfigValidateTest {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Root: []string{"testdata/secrets/root_ca.crt"}, Root: []string{"../testdata/secrets/root_ca.crt"},
IntermediateCert: "testdata/secrets/intermediate_ca.crt", IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
IntermediateKey: "testdata/secrets/intermediate_ca_key", IntermediateKey: "../testdata/secrets/intermediate_ca_key",
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
@ -53,9 +55,9 @@ func TestConfigValidate(t *testing.T) {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Address: "127.0.0.1", Address: "127.0.0.1",
Root: []string{"testdata/secrets/root_ca.crt"}, Root: []string{"../testdata/secrets/root_ca.crt"},
IntermediateCert: "testdata/secrets/intermediate_ca.crt", IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
IntermediateKey: "testdata/secrets/intermediate_ca_key", IntermediateKey: "../testdata/secrets/intermediate_ca_key",
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
@ -67,8 +69,8 @@ func TestConfigValidate(t *testing.T) {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Address: "127.0.0.1:443", Address: "127.0.0.1:443",
IntermediateCert: "testdata/secrets/intermediate_ca.crt", IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
IntermediateKey: "testdata/secrets/intermediate_ca_key", IntermediateKey: "../testdata/secrets/intermediate_ca_key",
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
@ -80,8 +82,8 @@ func TestConfigValidate(t *testing.T) {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Address: "127.0.0.1:443", Address: "127.0.0.1:443",
Root: []string{"testdata/secrets/root_ca.crt"}, Root: []string{"../testdata/secrets/root_ca.crt"},
IntermediateKey: "testdata/secrets/intermediate_ca_key", IntermediateKey: "../testdata/secrets/intermediate_ca_key",
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
@ -93,8 +95,8 @@ func TestConfigValidate(t *testing.T) {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Address: "127.0.0.1:443", Address: "127.0.0.1:443",
Root: []string{"testdata/secrets/root_ca.crt"}, Root: []string{"../testdata/secrets/root_ca.crt"},
IntermediateCert: "testdata/secrets/intermediate_ca.crt", IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
@ -106,9 +108,9 @@ func TestConfigValidate(t *testing.T) {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Address: "127.0.0.1:443", Address: "127.0.0.1:443",
Root: []string{"testdata/secrets/root_ca.crt"}, Root: []string{"../testdata/secrets/root_ca.crt"},
IntermediateCert: "testdata/secrets/intermediate_ca.crt", IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
IntermediateKey: "testdata/secrets/intermediate_ca_key", IntermediateKey: "../testdata/secrets/intermediate_ca_key",
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
}, },
@ -119,9 +121,9 @@ func TestConfigValidate(t *testing.T) {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Address: "127.0.0.1:443", Address: "127.0.0.1:443",
Root: []string{"testdata/secrets/root_ca.crt"}, Root: []string{"../testdata/secrets/root_ca.crt"},
IntermediateCert: "testdata/secrets/intermediate_ca.crt", IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
IntermediateKey: "testdata/secrets/intermediate_ca_key", IntermediateKey: "../testdata/secrets/intermediate_ca_key",
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
@ -133,9 +135,9 @@ func TestConfigValidate(t *testing.T) {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Address: "127.0.0.1:443", Address: "127.0.0.1:443",
Root: []string{"testdata/secrets/root_ca.crt"}, Root: []string{"../testdata/secrets/root_ca.crt"},
IntermediateCert: "testdata/secrets/intermediate_ca.crt", IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
IntermediateKey: "testdata/secrets/intermediate_ca_key", IntermediateKey: "../testdata/secrets/intermediate_ca_key",
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
@ -148,9 +150,9 @@ func TestConfigValidate(t *testing.T) {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Address: "127.0.0.1:443", Address: "127.0.0.1:443",
Root: []string{"testdata/secrets/root_ca.crt"}, Root: []string{"../testdata/secrets/root_ca.crt"},
IntermediateCert: "testdata/secrets/intermediate_ca.crt", IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
IntermediateKey: "testdata/secrets/intermediate_ca_key", IntermediateKey: "../testdata/secrets/intermediate_ca_key",
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
@ -177,9 +179,9 @@ func TestConfigValidate(t *testing.T) {
return ConfigValidateTest{ return ConfigValidateTest{
config: &Config{ config: &Config{
Address: "127.0.0.1:443", Address: "127.0.0.1:443",
Root: []string{"testdata/secrets/root_ca.crt"}, Root: []string{"../testdata/secrets/root_ca.crt"},
IntermediateCert: "testdata/secrets/intermediate_ca.crt", IntermediateCert: "../testdata/secrets/intermediate_ca.crt",
IntermediateKey: "testdata/secrets/intermediate_ca_key", IntermediateKey: "../testdata/secrets/intermediate_ca_key",
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
Password: "pass", Password: "pass",
AuthorityConfig: ac, AuthorityConfig: ac,
@ -207,6 +209,8 @@ func TestConfigValidate(t *testing.T) {
} }
} else { } else {
if assert.Nil(t, tc.err) { 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) assert.Equals(t, *tc.config.TLS, tc.tls)
} }
} }
@ -224,9 +228,9 @@ func TestAuthConfigValidate(t *testing.T) {
CommonName: "test", CommonName: "test",
} }
maxjwk, err := jose.ReadKey("testdata/secrets/max_pub.jwk") maxjwk, err := jose.ReadKey("../testdata/secrets/max_pub.jwk")
assert.FatalError(t, err) 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) assert.FatalError(t, err)
p := provisioner.List{ p := provisioner.List{
&provisioner.JWK{ &provisioner.JWK{

94
authority/config/ssh.go Normal file
View 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
}

View 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)
}
})
}
}

View file

@ -1,4 +1,4 @@
package authority package config
import ( import (
"crypto/tls" "crypto/tls"
@ -34,6 +34,18 @@ var (
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", "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. // TLSVersion represents a TLS version number.

View file

@ -1,4 +1,4 @@
package authority package config
import ( import (
"crypto/tls" "crypto/tls"

View file

@ -1,4 +1,4 @@
package authority package config
import ( import (
"encoding/json" "encoding/json"

View file

@ -1,4 +1,4 @@
package authority package config
import ( import (
"reflect" "reflect"

View file

@ -7,6 +7,8 @@ import (
"encoding/pem" "encoding/pem"
"github.com/pkg/errors" "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/authority/provisioner"
"github.com/smallstep/certificates/cas" "github.com/smallstep/certificates/cas"
casapi "github.com/smallstep/certificates/cas/apiv1" 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 // WithConfig replaces the current config with the given one. No validation is
// performed in the given value. // performed in the given value.
func WithConfig(config *Config) Option { func WithConfig(config *config.Config) Option {
return func(a *Authority) error { return func(a *Authority) error {
a.config = config a.config = config
return nil return nil
@ -31,7 +33,7 @@ func WithConfig(config *Config) Option {
// the current one. No validation is performed in the given configuration. // the current one. No validation is performed in the given configuration.
func WithConfigFile(filename string) Option { func WithConfigFile(filename string) Option {
return func(a *Authority) (err error) { return func(a *Authority) (err error) {
a.config, err = LoadConfiguration(filename) a.config, err = config.LoadConfiguration(filename)
return 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 // WithSSHBastionFunc sets a custom function to get the bastion for a
// given user-host pair. // 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 { return func(a *Authority) error {
a.sshBastionFunc = fn a.sshBastionFunc = fn
return nil 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 // WithSSHGetHosts sets a custom function to get the bastion for a
// given user-host pair. // 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 { return func(a *Authority) error {
a.sshGetHostsFunc = fn a.sshGetHostsFunc = fn
return nil 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) { func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) {
var block *pem.Block var block *pem.Block
var certs []*x509.Certificate var certs []*x509.Certificate

View file

@ -13,6 +13,7 @@ import (
// provisioning flow. // provisioning flow.
type ACME struct { type ACME struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
ForceCN bool `json:"forceCN,omitempty"` ForceCN bool `json:"forceCN,omitempty"`
@ -23,6 +24,15 @@ type ACME struct {
// GetID returns the provisioner unique identifier. // GetID returns the provisioner unique identifier.
func (p ACME) GetID() string { 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 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. // certificate was configured to allow renewals.
func (p *ACME) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { func (p *ACME) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
if p.claimer.IsDisableRenewal() { 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 return nil
} }

View file

@ -61,7 +61,7 @@ func TestACME_Init(t *testing.T) {
"fail-bad-claims": func(t *testing.T) ProvisionerValidateTest { "fail-bad-claims": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{ return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar", Claims: &Claims{DefaultTLSDur: &Duration{0}}}, 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 { "ok": func(t *testing.T) ProvisionerValidateTest {
@ -110,7 +110,7 @@ func TestACME_AuthorizeRenew(t *testing.T) {
p: p, p: p,
cert: &x509.Certificate{}, cert: &x509.Certificate{},
code: http.StatusUnauthorized, 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 { "ok": func(t *testing.T) test {

View file

@ -252,6 +252,7 @@ type awsInstanceIdentityDocument struct {
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
type AWS struct { type AWS struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Accounts []string `json:"accounts"` Accounts []string `json:"accounts"`
@ -269,6 +270,15 @@ type AWS struct {
// GetID returns the provisioner unique identifier. // GetID returns the provisioner unique identifier.
func (p *AWS) GetID() string { 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 return "aws/" + p.Name
} }
@ -286,7 +296,7 @@ func (p *AWS) GetTokenID(token string) (string, error) {
} }
// Use provisioner + instance-id as the identifier. // 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)) sum := sha256.Sum256([]byte(unique))
return strings.ToLower(hex.EncodeToString(sum[:])), nil return strings.ToLower(hex.EncodeToString(sum[:])), nil
} }
@ -334,7 +344,7 @@ func (p *AWS) GetIdentityToken(subject, caURL string) (string, error) {
return "", err return "", err
} }
audience, err := generateSignAudience(caURL, p.GetID()) audience, err := generateSignAudience(caURL, p.GetIDForToken())
if err != nil { if err != nil {
return "", err 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 // 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 // per provisioner is allowed as we don't have a way to trust the given
// sans. // sans.
unique := fmt.Sprintf("%s.%s", p.GetID(), idoc.InstanceID) unique := fmt.Sprintf("%s.%s", p.GetIDForToken(), idoc.InstanceID)
sum := sha256.Sum256([]byte(unique)) sum := sha256.Sum256([]byte(unique))
// Create a JWT from the identity document // 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 { if p.config, err = newAWSConfig(p.IIDRoots); err != nil {
return err return err
} }
p.audiences = config.Audiences.WithFragment(p.GetID()) p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
// validate IMDS versions // validate IMDS versions
if len(p.IMDSVersions) == 0 { 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. // certificate was configured to allow renewals.
func (p *AWS) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { func (p *AWS) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
if p.claimer.IsDisableRenewal() { 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 return nil
} }
@ -687,7 +697,7 @@ func (p *AWS) authorizeToken(token string) (*awsPayload, error) {
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request. // AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { func (p *AWS) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
if !p.claimer.IsSSHCAEnabled() { 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) claims, err := p.authorizeToken(token)
if err != nil { if err != nil {

View file

@ -84,6 +84,7 @@ type azurePayload struct {
// and https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service // and https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service
type Azure struct { type Azure struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
TenantID string `json:"tenantID"` TenantID string `json:"tenantID"`
@ -101,6 +102,15 @@ type Azure struct {
// GetID returns the provisioner unique identifier. // GetID returns the provisioner unique identifier.
func (p *Azure) GetID() string { 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 return p.TenantID
} }
@ -324,7 +334,7 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption,
// certificate was configured to allow renewals. // certificate was configured to allow renewals.
func (p *Azure) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { func (p *Azure) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
if p.claimer.IsDisableRenewal() { 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 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. // AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { func (p *Azure) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
if !p.claimer.IsSSHCAEnabled() { 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) _, name, _, err := p.authorizeToken(token)

View file

@ -71,6 +71,9 @@ func (c *Claimer) DefaultTLSCertDuration() time.Duration {
// minimum from the authority configuration will be used. // minimum from the authority configuration will be used.
func (c *Claimer) MinTLSCertDuration() time.Duration { func (c *Claimer) MinTLSCertDuration() time.Duration {
if c.claims == nil || c.claims.MinTLSDur == nil { 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.global.MinTLSDur.Duration
} }
return c.claims.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. // maximum from the authority configuration will be used.
func (c *Claimer) MaxTLSCertDuration() time.Duration { func (c *Claimer) MaxTLSCertDuration() time.Duration {
if c.claims == nil || c.claims.MaxTLSDur == nil { 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.global.MaxTLSDur.Duration
} }
return c.claims.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. // global minimum from the authority configuration will be used.
func (c *Claimer) MinUserSSHCertDuration() time.Duration { func (c *Claimer) MinUserSSHCertDuration() time.Duration {
if c.claims == nil || c.claims.MinUserSSHDur == nil { 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.global.MinUserSSHDur.Duration
} }
return c.claims.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. // global maximum from the authority configuration will be used.
func (c *Claimer) MaxUserSSHCertDuration() time.Duration { func (c *Claimer) MaxUserSSHCertDuration() time.Duration {
if c.claims == nil || c.claims.MaxUserSSHDur == nil { 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.global.MaxUserSSHDur.Duration
} }
return c.claims.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. // global minimum from the authority configuration will be used.
func (c *Claimer) MinHostSSHCertDuration() time.Duration { func (c *Claimer) MinHostSSHCertDuration() time.Duration {
if c.claims == nil || c.claims.MinHostSSHDur == nil { 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.global.MinHostSSHDur.Duration
} }
return c.claims.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. // global maximum from the authority configuration will be used.
func (c *Claimer) MaxHostSSHCertDuration() time.Duration { func (c *Claimer) MaxHostSSHCertDuration() time.Duration {
if c.claims == nil || c.claims.MaxHostSSHDur == nil { 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.global.MaxHostSSHDur.Duration
} }
return c.claims.MaxHostSSHDur.Duration return c.claims.MaxHostSSHDur.Duration

View file

@ -12,7 +12,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/pkg/errors" "github.com/smallstep/certificates/authority/admin"
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
) )
@ -45,6 +45,8 @@ type loadByTokenPayload struct {
type Collection struct { type Collection struct {
byID *sync.Map byID *sync.Map
byKey *sync.Map byKey *sync.Map
byName *sync.Map
byTokenID *sync.Map
sorted provisionerSlice sorted provisionerSlice
audiences Audiences audiences Audiences
} }
@ -55,6 +57,8 @@ func NewCollection(audiences Audiences) *Collection {
return &Collection{ return &Collection{
byID: new(sync.Map), byID: new(sync.Map),
byKey: new(sync.Map), byKey: new(sync.Map),
byName: new(sync.Map),
byTokenID: new(sync.Map),
audiences: audiences, audiences: audiences,
} }
} }
@ -64,6 +68,18 @@ func (c *Collection) Load(id string) (Interface, bool) {
return loadProvisioner(c.byID, id) 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. // LoadByToken parses the token claims and loads the provisioner associated.
func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) (Interface, bool) { func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims) (Interface, bool) {
var audiences []string var audiences []string
@ -79,11 +95,12 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
if matchesAudience(claims.Audience, audiences) { if matchesAudience(claims.Audience, audiences) {
// Use fragment to get provisioner name (GCP, AWS, SSHPOP) // Use fragment to get provisioner name (GCP, AWS, SSHPOP)
if fragment != "" { if fragment != "" {
return c.Load(fragment) return c.LoadByTokenID(fragment)
} }
// If matches with stored audiences it will be a JWT token (default), and // If matches with stored audiences it will be a JWT token (default), and
// the id would be <issuer>:<kid>. // 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. // 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. // Kubernetes Service Account tokens.
if payload.Issuer == k8sSAIssuer { if payload.Issuer == k8sSAIssuer {
if p, ok := c.Load(K8sSAID); ok { if p, ok := c.LoadByTokenID(K8sSAID); ok {
return p, ok return p, ok
} }
// Kubernetes service account provisioner not found // Kubernetes service account provisioner not found
@ -108,18 +125,18 @@ func (c *Collection) LoadByToken(token *jose.JSONWebToken, claims *jose.Claims)
// Try with azp (OIDC) // Try with azp (OIDC)
if len(payload.AuthorizedParty) > 0 { if len(payload.AuthorizedParty) > 0 {
if p, ok := c.Load(payload.AuthorizedParty); ok { if p, ok := c.LoadByTokenID(payload.AuthorizedParty); ok {
return p, ok return p, ok
} }
} }
// Try with tid (Azure) // Try with tid (Azure)
if payload.TenantID != "" { if payload.TenantID != "" {
if p, ok := c.Load(payload.TenantID); ok { if p, ok := c.LoadByTokenID(payload.TenantID); ok {
return p, ok return p, ok
} }
} }
// Fallback to aud // Fallback to aud
return c.Load(payload.Audience[0]) return c.LoadByTokenID(payload.Audience[0])
} }
// LoadByCertificate looks for the provisioner extension and extracts the // 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 { if _, err := asn1.Unmarshal(e.Value, &provisioner); err != nil {
return nil, false return nil, false
} }
switch Type(provisioner.Type) { return c.LoadByName(string(provisioner.Name))
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))
}
} }
} }
@ -173,7 +173,21 @@ func (c *Collection) LoadEncryptedKey(keyID string) (string, bool) {
func (c *Collection) Store(p Interface) error { func (c *Collection) Store(p Interface) error {
// Store provisioner always in byID. ID must be unique. // Store provisioner always in byID. ID must be unique.
if _, loaded := c.byID.LoadOrStore(p.GetID(), p); loaded { 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. // Store provisioner in byKey if EncryptedKey is defined.
@ -197,6 +211,65 @@ func (c *Collection) Store(p Interface) error {
return nil 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. // Find implements pagination on a list of sorted provisioners.
func (c *Collection) Find(cursor string, limit int) (List, string) { func (c *Collection) Find(cursor string, limit int) (List, string) {
switch { switch {

View file

@ -132,6 +132,7 @@ func TestCollection_LoadByToken(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Collection{ c := &Collection{
byID: tt.fields.byID, byID: tt.fields.byID,
byTokenID: tt.fields.byID,
audiences: tt.fields.audiences, audiences: tt.fields.audiences,
} }
got, got1 := c.LoadByToken(tt.args.token, tt.args.claims) got, got1 := c.LoadByToken(tt.args.token, tt.args.claims)
@ -153,10 +154,10 @@ func TestCollection_LoadByCertificate(t *testing.T) {
p3, err := generateACME() p3, err := generateACME()
assert.FatalError(t, err) assert.FatalError(t, err)
byID := new(sync.Map) byName := new(sync.Map)
byID.Store(p1.GetID(), p1) byName.Store(p1.GetName(), p1)
byID.Store(p2.GetID(), p2) byName.Store(p2.GetName(), p2)
byID.Store(p3.GetID(), p3) byName.Store(p3.GetName(), p3)
ok1Ext, err := createProvisionerExtension(1, p1.Name, p1.Key.KeyID) ok1Ext, err := createProvisionerExtension(1, p1.Name, p1.Key.KeyID)
assert.FatalError(t, err) assert.FatalError(t, err)
@ -186,7 +187,7 @@ func TestCollection_LoadByCertificate(t *testing.T) {
} }
type fields struct { type fields struct {
byID *sync.Map byName *sync.Map
audiences Audiences audiences Audiences
} }
type args struct { type args struct {
@ -199,17 +200,17 @@ func TestCollection_LoadByCertificate(t *testing.T) {
want Interface want Interface
want1 bool want1 bool
}{ }{
{"ok1", fields{byID, testAudiences}, args{ok1Cert}, p1, true}, {"ok1", fields{byName, testAudiences}, args{ok1Cert}, p1, true},
{"ok2", fields{byID, testAudiences}, args{ok2Cert}, p2, true}, {"ok2", fields{byName, testAudiences}, args{ok2Cert}, p2, true},
{"ok3", fields{byID, testAudiences}, args{ok3Cert}, p3, true}, {"ok3", fields{byName, testAudiences}, args{ok3Cert}, p3, true},
{"noExtension", fields{byID, testAudiences}, args{&x509.Certificate{}}, &noop{}, true}, {"noExtension", fields{byName, testAudiences}, args{&x509.Certificate{}}, &noop{}, true},
{"notFound", fields{byID, testAudiences}, args{notFoundCert}, nil, false}, {"notFound", fields{byName, testAudiences}, args{notFoundCert}, nil, false},
{"badCert", fields{byID, testAudiences}, args{badCert}, nil, false}, {"badCert", fields{byName, testAudiences}, args{badCert}, nil, false},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
c := &Collection{ c := &Collection{
byID: tt.fields.byID, byName: tt.fields.byName,
audiences: tt.fields.audiences, audiences: tt.fields.audiences,
} }
got, got1 := c.LoadByCertificate(tt.args.cert) got, got1 := c.LoadByCertificate(tt.args.cert)

View file

@ -78,6 +78,7 @@ func newGCPConfig() *gcpConfig {
// https://cloud.google.com/compute/docs/instances/verifying-instance-identity // https://cloud.google.com/compute/docs/instances/verifying-instance-identity
type GCP struct { type GCP struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
ServiceAccounts []string `json:"serviceAccounts"` ServiceAccounts []string `json:"serviceAccounts"`
@ -96,6 +97,16 @@ type GCP struct {
// GetID returns the provisioner unique identifier. The name should uniquely // GetID returns the provisioner unique identifier. The name should uniquely
// identify any GCP provisioner. // identify any GCP provisioner.
func (p *GCP) GetID() string { 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 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 // 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 // per provisioner is allowed as we don't have a way to trust the given
// sans. // 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)) sum := sha256.Sum256([]byte(unique))
return strings.ToLower(hex.EncodeToString(sum[:])), nil 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. // GetIdentityToken does an HTTP request to the identity url.
func (p *GCP) GetIdentityToken(subject, caURL string) (string, error) { func (p *GCP) GetIdentityToken(subject, caURL string) (string, error) {
audience, err := generateSignAudience(caURL, p.GetID()) audience, err := generateSignAudience(caURL, p.GetIDForToken())
if err != nil { if err != nil {
return "", err return "", err
} }
@ -205,7 +216,7 @@ func (p *GCP) Init(config Config) error {
return err return err
} }
p.audiences = config.Audiences.WithFragment(p.GetID()) p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
return nil 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. // AuthorizeRenew returns an error if the renewal is disabled.
func (p *GCP) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { func (p *GCP) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
if p.claimer.IsDisableRenewal() { 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 return nil
} }
@ -371,7 +382,7 @@ func (p *GCP) authorizeToken(token string) (*gcpPayload, error) {
// AuthorizeSSHSign returns the list of SignOption for a SignSSH request. // AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { func (p *GCP) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
if !p.claimer.IsSSHCAEnabled() { 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) claims, err := p.authorizeToken(token)
if err != nil { if err != nil {

View file

@ -28,6 +28,7 @@ type stepPayload struct {
// signature requests. // signature requests.
type JWK struct { type JWK struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Key *jose.JSONWebKey `json:"key"` Key *jose.JSONWebKey `json:"key"`
@ -41,6 +42,15 @@ type JWK struct {
// GetID returns the provisioner unique identifier. The name and credential id // GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any JWK provisioner. // should uniquely identify any JWK provisioner.
func (p *JWK) GetID() string { 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 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. // certificate was configured to allow renewals.
func (p *JWK) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { func (p *JWK) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
if p.claimer.IsDisableRenewal() { 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 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. // AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { func (p *JWK) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
if !p.claimer.IsSSHCAEnabled() { 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) claims, err := p.authorizeToken(token, p.audiences.SSHSign)
if err != nil { if err != nil {

View file

@ -77,7 +77,7 @@ func TestJWK_Init(t *testing.T) {
"fail-bad-claims": func(t *testing.T) ProvisionerValidateTest { "fail-bad-claims": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{ return ProvisionerValidateTest{
p: &JWK{Name: "foo", Type: "bar", Key: &jose.JSONWebKey{}, audiences: testAudiences, Claims: &Claims{DefaultTLSDur: &Duration{0}}}, 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 { "ok": func(t *testing.T) ProvisionerValidateTest {

View file

@ -42,6 +42,7 @@ type k8sSAPayload struct {
// entity trusted to make signature requests. // entity trusted to make signature requests.
type K8sSA struct { type K8sSA struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
PubKeys []byte `json:"publicKeys,omitempty"` PubKeys []byte `json:"publicKeys,omitempty"`
@ -56,6 +57,15 @@ type K8sSA struct {
// GetID returns the provisioner unique identifier. The name and credential id // GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any K8sSA provisioner. // should uniquely identify any K8sSA provisioner.
func (p *K8sSA) GetID() string { 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 return K8sSAID
} }
@ -101,12 +111,12 @@ func (p *K8sSA) Init(config Config) (err error) {
} }
key, err := pemutil.ParseKey(pem.EncodeToMemory(block)) key, err := pemutil.ParseKey(pem.EncodeToMemory(block))
if err != nil { 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) { switch q := key.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
default: 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) 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. // AuthorizeRenew returns an error if the renewal is disabled.
func (p *K8sSA) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { func (p *K8sSA) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
if p.claimer.IsDisableRenewal() { 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 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. // AuthorizeSSHSign validates an request for an SSH certificate.
func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { func (p *K8sSA) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
if !p.claimer.IsSSHCAEnabled() { 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) claims, err := p.authorizeToken(token, p.audiences.SSHSign)
if err != nil { if err != nil {

View file

@ -198,7 +198,7 @@ func TestK8sSA_AuthorizeRenew(t *testing.T) {
p: p, p: p,
cert: &x509.Certificate{}, cert: &x509.Certificate{},
code: http.StatusUnauthorized, 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 { "ok": func(t *testing.T) test {
@ -319,7 +319,7 @@ func TestK8sSA_AuthorizeSSHSign(t *testing.T) {
p: p, p: p,
token: "foo", token: "foo",
code: http.StatusUnauthorized, 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 { "fail/invalid-token": func(t *testing.T) test {

View file

@ -14,6 +14,10 @@ func (p *noop) GetID() string {
return "noop" return "noop"
} }
func (p *noop) GetIDForToken() string {
return "noop"
}
func (p *noop) GetTokenID(token string) (string, error) { func (p *noop) GetTokenID(token string) (string, error) {
return "", nil return "", nil
} }

View file

@ -54,6 +54,7 @@ type openIDPayload struct {
// ClientSecret is mandatory, but it can be an empty string. // ClientSecret is mandatory, but it can be an empty string.
type OIDC struct { type OIDC struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
ClientID string `json:"clientID"` ClientID string `json:"clientID"`
@ -111,6 +112,15 @@ func sanitizeEmail(email string) string {
// GetID returns the provisioner unique identifier, the OIDC provisioner the // GetID returns the provisioner unique identifier, the OIDC provisioner the
// uses the clientID for this. // uses the clientID for this.
func (o *OIDC) GetID() string { 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 return o.ClientID
} }
@ -367,7 +377,7 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e
// certificate was configured to allow renewals. // certificate was configured to allow renewals.
func (o *OIDC) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { func (o *OIDC) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
if o.claimer.IsDisableRenewal() { 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 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. // AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { func (o *OIDC) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
if !o.claimer.IsSSHCAEnabled() { 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) claims, err := o.authorizeToken(token)
if err != nil { if err != nil {

View file

@ -17,6 +17,7 @@ import (
// Interface is the interface that all provisioner types must implement. // Interface is the interface that all provisioner types must implement.
type Interface interface { type Interface interface {
GetID() string GetID() string
GetIDForToken() string
GetTokenID(token string) (string, error) GetTokenID(token string) (string, error)
GetName() string GetName() string
GetType() Type GetType() Type
@ -394,6 +395,7 @@ type MockProvisioner struct {
Mret1, Mret2, Mret3 interface{} Mret1, Mret2, Mret3 interface{}
Merr error Merr error
MgetID func() string MgetID func() string
MgetIDForToken func() string
MgetTokenID func(string) (string, error) MgetTokenID func(string) (string, error)
MgetName func() string MgetName func() string
MgetType func() Type MgetType func() Type
@ -416,6 +418,14 @@ func (m *MockProvisioner) GetID() string {
return m.Mret1.(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 // GetTokenID mock
func (m *MockProvisioner) GetTokenID(token string) (string, error) { func (m *MockProvisioner) GetTokenID(token string) (string, error) {
if m.MgetTokenID != nil { if m.MgetTokenID != nil {

View file

@ -11,6 +11,7 @@ import (
// SCEP provisioning flow // SCEP provisioning flow
type SCEP struct { type SCEP struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
@ -27,7 +28,16 @@ type SCEP struct {
} }
// GetID returns the provisioner unique identifier. // 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 return "scep/" + s.Name
} }

View file

@ -26,6 +26,7 @@ type sshPOPPayload struct {
// signature requests. // signature requests.
type SSHPOP struct { type SSHPOP struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Claims *Claims `json:"claims,omitempty"` Claims *Claims `json:"claims,omitempty"`
@ -38,6 +39,15 @@ type SSHPOP struct {
// GetID returns the provisioner unique identifier. The name and credential id // GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any SSH-POP provisioner. // should uniquely identify any SSH-POP provisioner.
func (p *SSHPOP) GetID() string { 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 return "sshpop/" + p.Name
} }
@ -91,7 +101,7 @@ func (p *SSHPOP) Init(config Config) error {
return err return err
} }
p.audiences = config.Audiences.WithFragment(p.GetID()) p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
p.db = config.DB p.db = config.DB
p.sshPubKeys = config.SSHKeys p.sshPubKeys = config.SSHKeys
return nil return nil

View file

@ -26,6 +26,7 @@ type x5cPayload struct {
// signature requests. // signature requests.
type X5C struct { type X5C struct {
*base *base
ID string `json:"-"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Roots []byte `json:"roots"` Roots []byte `json:"roots"`
@ -39,6 +40,15 @@ type X5C struct {
// GetID returns the provisioner unique identifier. The name and credential id // GetID returns the provisioner unique identifier. The name and credential id
// should uniquely identify any X5C provisioner. // should uniquely identify any X5C provisioner.
func (p *X5C) GetID() string { 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 return "x5c/" + p.Name
} }
@ -106,7 +116,7 @@ func (p *X5C) Init(config Config) error {
// Verify that at least one root was found. // Verify that at least one root was found.
if len(p.rootPool.Subjects()) == 0 { 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 // Update claims with global ones
@ -115,7 +125,7 @@ func (p *X5C) Init(config Config) error {
return err return err
} }
p.audiences = config.Audiences.WithFragment(p.GetID()) p.audiences = config.Audiences.WithFragment(p.GetIDForToken())
return nil return nil
} }
@ -129,7 +139,8 @@ func (p *X5C) authorizeToken(token string, audiences []string) (*x5cPayload, err
} }
verifiedChains, err := jwt.Headers[0].Certificates(x509.VerifyOptions{ verifiedChains, err := jwt.Headers[0].Certificates(x509.VerifyOptions{
Roots: p.rootPool, Roots: p.rootPool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}) })
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusUnauthorized, err, 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. // AuthorizeRenew returns an error if the renewal is disabled.
func (p *X5C) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error { func (p *X5C) AuthorizeRenew(ctx context.Context, cert *x509.Certificate) error {
if p.claimer.IsDisableRenewal() { 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 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. // AuthorizeSSHSign returns the list of SignOption for a SignSSH request.
func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) { func (p *X5C) AuthorizeSSHSign(ctx context.Context, token string) ([]SignOption, error) {
if !p.claimer.IsSSHCAEnabled() { 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) claims, err := p.authorizeToken(token, p.audiences.SSHSign)

View file

@ -70,7 +70,7 @@ func TestX5C_Init(t *testing.T) {
"fail/no-valid-root-certs": func(t *testing.T) ProvisionerValidateTest { "fail/no-valid-root-certs": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{ return ProvisionerValidateTest{
p: &X5C{Name: "foo", Type: "bar", Roots: []byte("foo"), audiences: testAudiences}, 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 { "fail/invalid-duration": func(t *testing.T) ProvisionerValidateTest {
@ -79,7 +79,7 @@ func TestX5C_Init(t *testing.T) {
p.Claims = &Claims{DefaultTLSDur: &Duration{0}} p.Claims = &Claims{DefaultTLSDur: &Duration{0}}
return ProvisionerValidateTest{ return ProvisionerValidateTest{
p: p, 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 { "ok": func(t *testing.T) ProvisionerValidateTest {
@ -568,7 +568,7 @@ func TestX5C_AuthorizeRenew(t *testing.T) {
return test{ return test{
p: p, p: p,
code: http.StatusUnauthorized, 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 { "ok": func(t *testing.T) test {
@ -624,7 +624,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) {
p: p, p: p,
token: "foo", token: "foo",
code: http.StatusUnauthorized, 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 { "fail/invalid-token": func(t *testing.T) test {

View file

@ -1,14 +1,24 @@
package authority package authority
import ( import (
"context"
"crypto/x509" "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/authority/provisioner"
"github.com/smallstep/certificates/errs" "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. // GetEncryptedKey returns the JWE key corresponding to the given kid argument.
func (a *Authority) GetEncryptedKey(kid string) (string, error) { func (a *Authority) GetEncryptedKey(kid string) (string, error) {
a.adminMutex.RLock()
defer a.adminMutex.RUnlock()
key, ok := a.provisioners.LoadEncryptedKey(kid) key, ok := a.provisioners.LoadEncryptedKey(kid)
if !ok { if !ok {
return "", errs.NotFound("encrypted key with kid %s was not found", kid) 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 // GetProvisioners returns a map listing each provisioner and the JWK Key Set
// with their public keys. // with their public keys.
func (a *Authority) GetProvisioners(cursor string, limit int) (provisioner.List, string, error) { 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) provisioners, nextCursor := a.provisioners.Find(cursor, limit)
return provisioners, nextCursor, nil 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 // LoadProvisionerByCertificate returns an interface to the provisioner that
// provisioned the certificate. // provisioned the certificate.
func (a *Authority) LoadProvisionerByCertificate(crt *x509.Certificate) (provisioner.Interface, error) { func (a *Authority) LoadProvisionerByCertificate(crt *x509.Certificate) (provisioner.Interface, error) {
a.adminMutex.RLock()
defer a.adminMutex.RUnlock()
p, ok := a.provisioners.LoadByCertificate(crt) p, ok := a.provisioners.LoadByCertificate(crt)
if !ok { 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 return p, nil
} }
// LoadProvisionerByID returns an interface to the provisioner with the given ID. // LoadProvisionerByID returns an interface to the provisioner with the given ID.
func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) { func (a *Authority) LoadProvisionerByID(id string) (provisioner.Interface, error) {
a.adminMutex.RLock()
defer a.adminMutex.RUnlock()
p, ok := a.provisioners.Load(id) p, ok := a.provisioners.Load(id)
if !ok { if !ok {
return nil, errs.NotFound("provisioner not found") return nil, admin.NewError(admin.ErrorNotFoundType, "provisioner %s not found", id)
} }
return p, nil 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
}

View file

@ -56,7 +56,7 @@ func TestGetEncryptedKey(t *testing.T) {
} }
} else { } else {
if assert.Nil(t, tc.err) { 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) assert.Fatal(t, ok)
p, ok := val.(*provisioner.JWK) p, ok := val.(*provisioner.JWK)
assert.Fatal(t, ok) assert.Fatal(t, ok)

View file

@ -10,11 +10,11 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"github.com/smallstep/certificates/templates" "github.com/smallstep/certificates/templates"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/randutil" "go.step.sm/crypto/randutil"
"go.step.sm/crypto/sshutil" "go.step.sm/crypto/sshutil"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@ -32,103 +32,17 @@ const (
SSHAddUserCommand = "sudo useradd -m <principal>; nc -q0 localhost 22" 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. // GetSSHRoots returns the SSH User and Host public keys.
func (a *Authority) GetSSHRoots(context.Context) (*SSHKeys, error) { func (a *Authority) GetSSHRoots(context.Context) (*config.SSHKeys, error) {
return &SSHKeys{ return &config.SSHKeys{
HostKeys: a.sshCAHostCerts, HostKeys: a.sshCAHostCerts,
UserKeys: a.sshCAUserCerts, UserKeys: a.sshCAUserCerts,
}, nil }, nil
} }
// GetSSHFederation returns the public keys for federated SSH signers. // GetSSHFederation returns the public keys for federated SSH signers.
func (a *Authority) GetSSHFederation(context.Context) (*SSHKeys, error) { func (a *Authority) GetSSHFederation(context.Context) (*config.SSHKeys, error) {
return &SSHKeys{ return &config.SSHKeys{
HostKeys: a.sshCAHostFederatedCerts, HostKeys: a.sshCAHostFederatedCerts,
UserKeys: a.sshCAUserFederatedCerts, UserKeys: a.sshCAUserFederatedCerts,
}, nil }, 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, // GetSSHBastion returns the bastion configuration, for the given pair user,
// hostname. // 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 { if a.sshBastionFunc != nil {
bs, err := a.sshBastionFunc(ctx, user, hostname) bs, err := a.sshBastionFunc(ctx, user, hostname)
return bs, errs.Wrap(http.StatusInternalServerError, err, "authority.GetSSHBastion") 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. // 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 { if a.sshGetHostsFunc != nil {
hosts, err := a.sshGetHostsFunc(ctx, cert) hosts, err := a.sshGetHostsFunc(ctx, cert)
return hosts, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts") 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") return nil, errs.Wrap(http.StatusInternalServerError, err, "getSSHHosts")
} }
hosts := make([]Host, len(hostnames)) hosts := make([]config.Host, len(hostnames))
for i, hn := range hostnames { for i, hn := range hostnames {
hosts[i] = Host{Hostname: hn} hosts[i] = config.Host{Hostname: hn}
} }
return hosts, nil return hosts, nil
} }

View file

@ -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) { func TestAuthority_GetSSHBastion(t *testing.T) {
bastion := &Bastion{ bastion := &Bastion{
Hostname: "bastion.local", Hostname: "bastion.local",

View 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")
)

View file

@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
casapi "github.com/smallstep/certificates/cas/apiv1" casapi "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/db" "github.com/smallstep/certificates/db"
@ -23,14 +24,14 @@ import (
) )
// GetTLSOptions returns the tls options configured. // GetTLSOptions returns the tls options configured.
func (a *Authority) GetTLSOptions() *TLSOptions { func (a *Authority) GetTLSOptions() *config.TLSOptions {
return a.config.TLS return a.config.TLS
} }
var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35} var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35}
var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} 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 { return func(crt *x509.Certificate, opts provisioner.SignOptions) error {
if def == nil { if def == nil {
return errors.New("default ASN1DN template cannot be 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. // This method will also validate the audiences for JWK provisioners.
var ok bool p, err = a.LoadProvisionerByToken(token, &claims.Claims)
p, ok = a.provisioners.LoadByToken(token, &claims.Claims) if err != nil {
if !ok { return err
return errs.InternalServer("authority.Revoke; provisioner not found", opts...)
} }
rci.ProvisionerID = p.GetID() rci.ProvisionerID = p.GetID()
rci.TokenID, err = p.GetTokenID(revokeOpts.OTT) rci.TokenID, err = p.GetTokenID(revokeOpts.OTT)

View file

@ -656,7 +656,7 @@ func TestAuthority_Renew(t *testing.T) {
"fail/unauthorized": func() (*renewTest, error) { "fail/unauthorized": func() (*renewTest, error) {
return &renewTest{ return &renewTest{
cert: certNoRenew, 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, code: http.StatusUnauthorized,
}, nil }, nil
}, },
@ -856,7 +856,7 @@ func TestAuthority_Rekey(t *testing.T) {
"fail/unauthorized": func() (*renewTest, error) { "fail/unauthorized": func() (*renewTest, error) {
return &renewTest{ return &renewTest{
cert: certNoRenew, 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, code: http.StatusUnauthorized,
}, nil }, nil
}, },

568
ca/adminClient.go Normal file
View 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)
}

View file

@ -39,6 +39,53 @@ func Bootstrap(token string) (*Client, error) {
return NewClient(claims.Audience[0], WithRootSHA256(claims.SHA)) 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 // BootstrapServer is a helper function that using the given token returns the
// given http.Server configured with a TLS certificate signed by the Certificate // 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 // 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 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 // BootstrapListener is a helper function that using the given token returns a
// TLS listener which accepts connections from an inner listener and wraps each // TLS listener which accepts connections from an inner listener and wraps each
// connection with Server. // connection with Server.

View file

@ -17,6 +17,8 @@ import (
acmeNoSQL "github.com/smallstep/certificates/acme/db/nosql" acmeNoSQL "github.com/smallstep/certificates/acme/db/nosql"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority" "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/db"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/monitoring" "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. // the HTTP server, set ups the middlewares and the HTTP handlers.
type CA struct { type CA struct {
auth *authority.Authority auth *authority.Authority
config *authority.Config config *config.Config
srv *server.Server srv *server.Server
insecureSrv *server.Server insecureSrv *server.Server
opts *options opts *options
@ -85,7 +87,7 @@ type CA struct {
} }
// New creates and initializes the CA with the given configuration and options. // 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{ ca := &CA{
config: config, config: config,
opts: new(options), opts: new(options),
@ -95,7 +97,7 @@ func New(config *authority.Config, opts ...Option) (*CA, error) {
} }
// Init initializes the CA with the given configuration. // 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. // Intermediate Password.
if len(ca.opts.password) > 0 { if len(ca.opts.password) > 0 {
ca.config.Password = string(ca.opts.password) 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) dns = fmt.Sprintf("%s:%s", dns, port)
} }
// ACME Router
prefix := "acme" prefix := "acme"
var acmeDB acme.DB var acmeDB acme.DB
if config.DB == nil { if config.DB == nil {
@ -175,6 +178,17 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
acmeHandler.Route(r) 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() { if ca.shouldServeSCEPEndpoints() {
scepPrefix := "scep" scepPrefix := "scep"
scepAuthority, err := scep.New(auth, scep.AuthorityOptions{ 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. // Run starts the CA calling to the server ListenAndServe method.
func (ca *CA) Run() error { func (ca *CA) Run() error {
var wg sync.WaitGroup var wg sync.WaitGroup
errors := make(chan error, 1) 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 // Reload reloads the configuration of the CA and calls to the server Reload
// method. // method.
func (ca *CA) Reload() error { func (ca *CA) Reload() error {
config, err := authority.LoadConfiguration(ca.opts.configFile) config, err := config.LoadConfiguration(ca.opts.configFile)
if err != nil { if err != nil {
return errors.Wrap(err, "error reloading ca configuration") return errors.Wrap(err, "error reloading ca configuration")
} }

View file

@ -10,8 +10,10 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"encoding/pem"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -28,10 +30,13 @@ import (
"github.com/smallstep/certificates/ca/identity" "github.com/smallstep/certificates/ca/identity"
"github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/errs"
"go.step.sm/cli-utils/config" "go.step.sm/cli-utils/config"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil" "go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"gopkg.in/square/go-jose.v2/jwt" "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) 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 // RetryFunc defines the method used to retry a request. If it returns true, the
// request will be retried once. // request will be retried once.
type RetryFunc func(code int) bool type RetryFunc func(code int) bool
@ -103,6 +113,12 @@ type clientOptions struct {
certificate tls.Certificate certificate tls.Certificate
getClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error) getClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
retryFunc RetryFunc retryFunc RetryFunc
x5cJWK *jose.JSONWebKey
x5cCertFile string
x5cCertStrs []string
x5cCert *x509.Certificate
x5cIssuer string
x5cSubject string
} }
func (o *clientOptions) apply(opts []ClientOption) (err error) { 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 // WithCertificate will set the given certificate as the TLS client certificate
// in the client. // in the client.
func WithCertificate(crt tls.Certificate) ClientOption { func WithCertificate(cert tls.Certificate) ClientOption {
return func(o *clientOptions) error { 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 return nil
} }
} }
@ -367,6 +440,8 @@ type ProvisionerOption func(o *provisionerOptions) error
type provisionerOptions struct { type provisionerOptions struct {
cursor string cursor string
limit int limit int
id string
name string
} }
func (o *provisionerOptions) apply(opts []ProvisionerOption) (err error) { func (o *provisionerOptions) apply(opts []ProvisionerOption) (err error) {
@ -386,6 +461,12 @@ func (o *provisionerOptions) rawQuery() string {
if o.limit > 0 { if o.limit > 0 {
v.Set("limit", strconv.Itoa(o.limit)) 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() 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. // Client implements an HTTP client for the CA server.
type Client struct { type Client struct {
client *uaClient client *uaClient
@ -1206,6 +1303,15 @@ func readJSON(r io.ReadCloser, v interface{}) error {
return json.NewDecoder(r).Decode(v) 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 { func readError(r io.ReadCloser) error {
defer r.Close() defer r.Close()
apiErr := new(errs.Error) apiErr := new(errs.Error)

4
ca/testdata/ca.json vendored
View file

@ -34,7 +34,7 @@
"y": "ZhYcFQBqtErdC_pA7sOXrO7AboCEPIKP9Ik4CHJqANk" "y": "ZhYcFQBqtErdC_pA7sOXrO7AboCEPIKP9Ik4CHJqANk"
} }
}, { }, {
"name": "max", "name": "mike",
"type": "jwk", "type": "jwk",
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlZsWnl0dUxrWTR5enlqZXJybnN0aGcifQ.QP15wQYjZ12BLgl-XTq2Vb12G3OHAfic.X35QqAaXwnlmeCUU._2qIUp0TI8yDI7c2e9upIRdrnmB5OvtLfrYN-Su2NLBpaoYtr9O55Wo0Iryc0W2pYqnVDPvgPPes4P4nQAnzw5WhFYc1Xf1ZEetfdNhwi1x2FNwPbACBAgxm5AW40O5AAlbLcWushYASfeMBZocTGXuSGUzwFqoWD-5EDJ80TWQ7cAj3ttHrJ_3QV9hi4O9KJUCiXngN-Yz2zXrhBL4NOH2fmRbaf5c0rF8xUJIIW-TcyYJeX_Fbx1IzzKKPd9USUwkDhxD4tLa51I345xVqjuwG1PEn6nF8JKqLRVUKEKFin-ShXrfE61KceyAvm4YhWKrbJWIm3bH5Hxaphy4.TexIrIhsRxJStpE3EJ925Q", "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6IlZsWnl0dUxrWTR5enlqZXJybnN0aGcifQ.QP15wQYjZ12BLgl-XTq2Vb12G3OHAfic.X35QqAaXwnlmeCUU._2qIUp0TI8yDI7c2e9upIRdrnmB5OvtLfrYN-Su2NLBpaoYtr9O55Wo0Iryc0W2pYqnVDPvgPPes4P4nQAnzw5WhFYc1Xf1ZEetfdNhwi1x2FNwPbACBAgxm5AW40O5AAlbLcWushYASfeMBZocTGXuSGUzwFqoWD-5EDJ80TWQ7cAj3ttHrJ_3QV9hi4O9KJUCiXngN-Yz2zXrhBL4NOH2fmRbaf5c0rF8xUJIIW-TcyYJeX_Fbx1IzzKKPd9USUwkDhxD4tLa51I345xVqjuwG1PEn6nF8JKqLRVUKEKFin-ShXrfE61KceyAvm4YhWKrbJWIm3bH5Hxaphy4.TexIrIhsRxJStpE3EJ925Q",
"key": { "key": {
@ -76,7 +76,7 @@
"minTLSCertDuration": "1s" "minTLSCertDuration": "1s"
} }
}, { }, {
"name": "mariano", "name": "maxey",
"type": "jwk", "type": "jwk",
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6Ik5SLTk5ZkVMSm1CLW1FZGllUlFFc3cifQ.Fr314BEUGTda4ICJl2uxFdjpEUGGqJEV.gBbu_DZE1ONDu14r.X-7MKMyokZIF1HTCVqqL0tTWgaC1ZGZBLLltd11ZUhQTswo_8kvgiTv3cFShj7ATF0tAY8HStyJmzLO8mKPVOPDXSwjdNsPriZclI6JWGi9iOu8pEiN9pZM6-itxan1JMcDUNg2U-P1BmKppHRbDKsOTivymfRyeUk51dBIlS54p5xNK1HFLc1YtWC1Rc_ngYVqOgqlhIrCHArAEBe3jrfUaH2ym-8fkVdwVqtxmte3XXK9g8FchsygRNnOKtRcr0TyzTUV-7bPi8_t02Zi-EHLFaSawVXWV_Qk1GeLYJR22Rp74beo-b5-lCNVp10btO0xdGySUWmCJ4v4_QZw.c8unwWycwtfdJMM_0b0fuA", "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjoxMDAwMDAsInAycyI6Ik5SLTk5ZkVMSm1CLW1FZGllUlFFc3cifQ.Fr314BEUGTda4ICJl2uxFdjpEUGGqJEV.gBbu_DZE1ONDu14r.X-7MKMyokZIF1HTCVqqL0tTWgaC1ZGZBLLltd11ZUhQTswo_8kvgiTv3cFShj7ATF0tAY8HStyJmzLO8mKPVOPDXSwjdNsPriZclI6JWGi9iOu8pEiN9pZM6-itxan1JMcDUNg2U-P1BmKppHRbDKsOTivymfRyeUk51dBIlS54p5xNK1HFLc1YtWC1Rc_ngYVqOgqlhIrCHArAEBe3jrfUaH2ym-8fkVdwVqtxmte3XXK9g8FchsygRNnOKtRcr0TyzTUV-7bPi8_t02Zi-EHLFaSawVXWV_Qk1GeLYJR22Rp74beo-b5-lCNVp10btO0xdGySUWmCJ4v4_QZw.c8unwWycwtfdJMM_0b0fuA",
"key": { "key": {

View file

@ -103,7 +103,6 @@ func (c *Client) getClientTLSConfig(ctx context.Context, sign *api.SignResponse,
return nil, nil, err return nil, nil, err
} }
// Update renew function with transport
tr := getDefaultTransport(tlsConfig) tr := getDefaultTransport(tlsConfig)
// Use mutable tls.Config on renew // Use mutable tls.Config on renew
tr.DialTLS = c.buildDialTLS(tlsCtx) // nolint:staticcheck tr.DialTLS = c.buildDialTLS(tlsCtx) // nolint:staticcheck

View file

@ -143,7 +143,11 @@ func newX5CSigner(certFile, keyFile, password string) (jose.Signer, error) {
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, errors.Wrap(err, "error validating x5c certificate chain and key") 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 := new(jose.SignerOptions)
so.WithType("JWT") so.WithType("JWT")
so.WithHeader("kid", kid) so.WithHeader("kid", kid)
so.WithHeader("x5c", certs) so.WithHeader("x5c", certStrs)
return newJoseSigner(signer, so) return newJoseSigner(signer, so)
} }

View file

@ -11,7 +11,7 @@ import (
"unicode" "unicode"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/ca"
"github.com/urfave/cli" "github.com/urfave/cli"
"go.step.sm/cli-utils/errs" "go.step.sm/cli-utils/errs"
@ -56,7 +56,7 @@ func appAction(ctx *cli.Context) error {
} }
configFile := ctx.Args().Get(0) configFile := ctx.Args().Get(0)
config, err := authority.LoadConfiguration(configFile) config, err := config.LoadConfiguration(configFile)
if err != nil { if err != nil {
fatal(err) fatal(err)
} }

View file

@ -9,7 +9,7 @@ import (
"os" "os"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/ca"
"github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/pki" "github.com/smallstep/certificates/pki"
@ -162,7 +162,7 @@ func onboardAction(ctx *cli.Context) error {
return nil return nil
} }
func onboardPKI(config onboardingConfiguration) (*authority.Config, string, error) { func onboardPKI(config onboardingConfiguration) (*config.Config, string, error) {
p, err := pki.New(apiv1.Options{ p, err := pki.New(apiv1.Options{
Type: apiv1.SoftCAS, Type: apiv1.SoftCAS,
IsCreator: true, IsCreator: true,

17
go.mod
View file

@ -26,19 +26,22 @@ require (
github.com/stretchr/testify v1.7.0 // indirect github.com/stretchr/testify v1.7.0 // indirect
github.com/urfave/cli v1.22.4 github.com/urfave/cli v1.22.4
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1
go.step.sm/cli-utils v0.2.0 go.step.sm/cli-utils v0.4.1
go.step.sm/crypto v0.8.3 go.step.sm/crypto v0.9.0
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420
google.golang.org/api v0.47.0 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/grpc v1.38.0
google.golang.org/protobuf v1.26.0 google.golang.org/protobuf v1.26.0
gopkg.in/square/go-jose.v2 v2.5.1 gopkg.in/square/go-jose.v2 v2.5.1
) )
// replace github.com/smallstep/nosql => ../nosql // 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 replace go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 => github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568

200
go.sum
View file

@ -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.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= 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/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 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 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/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 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0 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/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/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 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/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/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/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-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/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-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/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.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/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/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/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-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/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/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-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0= 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 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/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
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/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/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/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/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/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/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 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 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/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 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 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/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 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/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/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-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-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/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/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/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/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-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-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/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/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 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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.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 h1:KoJOtZf+6wpQaDTuOWGuo61GxcPBIfhwRxRTaTWGCTc=
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd/go.mod h1:YylP9MpCYGVZQrly/j/diqcdUetCRRePeBB0c2VGXsA= 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/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-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= 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 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 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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-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/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/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/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.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 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.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.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.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/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/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/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/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/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/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/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 h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 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 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-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-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.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.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.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 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= 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.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.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= 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.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 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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.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 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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/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.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.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/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/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-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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.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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 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.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.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.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.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 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/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 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.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= 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.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 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 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/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 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/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.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.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/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/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/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-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/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/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/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/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/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-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-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-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-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-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-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-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.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-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-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/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.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/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/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/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/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/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/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/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 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/hudl/fargo v1.3.0 h1:0U6+BtN6LhaYuTnIJq4Wyq5cpn6O2kWrxAtcqBmYY6w=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 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/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 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 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/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.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 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= 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/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.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.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/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.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/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/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 h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 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/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/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/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.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 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/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/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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 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/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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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-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/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 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 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 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/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/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 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo=
github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= 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.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 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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/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/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 h1:cRzcY0S5QX+0+J+7YC4P2uZSnfMup8S8zJu/bLFgOkA=
github.com/micromdm/scep/v2 v2.0.0/go.mod h1:ouaDs5tcjOjdHD/h8BGaQsWE87MUnQ/wMTMgfMMIpPc= 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/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 h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= 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/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 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 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.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-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/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/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/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 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/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 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 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-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/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 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/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/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.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/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-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/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.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/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/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 h1:IB0Fy+dClpBq9aEoIrLyQXzU34JyI1xVTanPLB/+jvU=
github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ= 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/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/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/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 h1:+MPqEswjYiS0S1FCTg8MIhMBMzxiVQ94rooFwvPPiWk=
github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= 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.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/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/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/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-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/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.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/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-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.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.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/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/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/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/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/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/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 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/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.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/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.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 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.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_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-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-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-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.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/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.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/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/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-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.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.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/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/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/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/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 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 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 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 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/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/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 h1:CmSpbxmewNQbzqztaY0bke1qzHhyNyC29wYgh17Gxfo=
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg= 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/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/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 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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/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 h1:cq6a3NwjFJxkVlWU1T4qGskcfEXr0fO1WqQrraDO1Po=
github.com/smallstep/nosql v0.3.6/go.mod h1:h1zC/Z54uNHc8euquLED4qJNCrMHd3nytA141ZZh4qQ= 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/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/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/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/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 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 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 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 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.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/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/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.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/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 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-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/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/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.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.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/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 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= 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/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/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.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 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/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/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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/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.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= 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.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.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@ -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.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 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.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.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0=
go.step.sm/crypto v0.8.3 h1:TO/OPlaUrYXhs8srGEFNyL6OWVQvRmEPCUONNnQUuEM= 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.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.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/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.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/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/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.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= 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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-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-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-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 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-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-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-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-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 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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-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-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-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-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 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-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-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-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-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 h1:KzwjikDymrEmYYbdyfievTwjEeGlu+OM6oiKBkF3Jfg=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 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-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 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 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/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 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-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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/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/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/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/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/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 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 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/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/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.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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/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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 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= 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= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

View file

@ -19,7 +19,7 @@ import (
"time" "time"
"github.com/pkg/errors" "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/authority/provisioner"
"github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/ca"
"github.com/smallstep/certificates/cas" "github.com/smallstep/certificates/cas"
@ -481,12 +481,12 @@ type caDefaults struct {
} }
// Option is the type for modifiers over the auth config object. // 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 // WithDefaultDB is a configuration modifier that adds a default DB stanza to
// the authority config. // the authority config.
func WithDefaultDB() Option { func WithDefaultDB() Option {
return func(c *authority.Config) error { return func(c *authconfig.Config) error {
c.DB = &db.Config{ c.DB = &db.Config{
Type: "badger", Type: "badger",
DataSource: GetDBPath(), DataSource: GetDBPath(),
@ -498,14 +498,14 @@ func WithDefaultDB() Option {
// WithoutDB is a configuration modifier that adds a default DB stanza to // WithoutDB is a configuration modifier that adds a default DB stanza to
// the authority config. // the authority config.
func WithoutDB() Option { func WithoutDB() Option {
return func(c *authority.Config) error { return func(c *authconfig.Config) error {
c.DB = nil c.DB = nil
return nil return nil
} }
} }
// GenerateConfig returns the step certificates configuration. // 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() key, err := p.ottPrivateKey.CompactSerialize()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error serializing private key") 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 authorityOptions = &p.casOptions
} }
config := &authority.Config{ config := &authconfig.Config{
Root: []string{p.root}, Root: []string{p.root},
FederatedRoots: []string{}, FederatedRoots: []string{},
IntermediateCert: p.intermediate, IntermediateCert: p.intermediate,
@ -535,22 +535,22 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
Type: "badger", Type: "badger",
DataSource: GetDBPath(), DataSource: GetDBPath(),
}, },
AuthorityConfig: &authority.AuthConfig{ AuthorityConfig: &authconfig.AuthConfig{
Options: authorityOptions, Options: authorityOptions,
DisableIssuedAtCheck: false, DisableIssuedAtCheck: false,
Provisioners: provisioner.List{prov}, Provisioners: provisioner.List{prov},
}, },
TLS: &authority.TLSOptions{ TLS: &authconfig.TLSOptions{
MinVersion: authority.DefaultTLSMinVersion, MinVersion: authconfig.DefaultTLSMinVersion,
MaxVersion: authority.DefaultTLSMaxVersion, MaxVersion: authconfig.DefaultTLSMaxVersion,
Renegotiation: authority.DefaultTLSRenegotiation, Renegotiation: authconfig.DefaultTLSRenegotiation,
CipherSuites: authority.DefaultTLSCipherSuites, CipherSuites: authconfig.DefaultTLSCipherSuites,
}, },
Templates: p.getTemplates(), Templates: p.getTemplates(),
} }
if p.enableSSH { if p.enableSSH {
enableSSHCA := true enableSSHCA := true
config.SSH = &authority.SSHConfig{ config.SSH = &authconfig.SSHConfig{
HostKey: p.sshHostKey, HostKey: p.sshHostKey,
UserKey: p.sshUserKey, UserKey: p.sshUserKey,
} }