forked from TrueCloudLab/certificates
Add ACME EAB policy
This commit is contained in:
parent
679e2945f2
commit
7df52dbb76
16 changed files with 622 additions and 56 deletions
|
@ -43,6 +43,23 @@ func KeyToID(jwk *jose.JSONWebKey) (string, error) {
|
||||||
return base64.RawURLEncoding.EncodeToString(kid), nil
|
return base64.RawURLEncoding.EncodeToString(kid), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PolicyNames contains ACME account level policy names
|
||||||
|
type PolicyNames struct {
|
||||||
|
DNSNames []string `json:"dns"`
|
||||||
|
IPRanges []string `json:"ips"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// X509Policy contains ACME account level X.509 policy
|
||||||
|
type X509Policy struct {
|
||||||
|
Allowed PolicyNames `json:"allowed"`
|
||||||
|
Denied PolicyNames `json:"denied"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy is an ACME Account level policy
|
||||||
|
type Policy struct {
|
||||||
|
X509 X509Policy `json:"x509"`
|
||||||
|
}
|
||||||
|
|
||||||
// ExternalAccountKey is an ACME External Account Binding key.
|
// ExternalAccountKey is an ACME External Account Binding key.
|
||||||
type ExternalAccountKey struct {
|
type ExternalAccountKey struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -52,6 +69,7 @@ type ExternalAccountKey struct {
|
||||||
KeyBytes []byte `json:"-"`
|
KeyBytes []byte `json:"-"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
BoundAt time.Time `json:"boundAt,omitempty"`
|
BoundAt time.Time `json:"boundAt,omitempty"`
|
||||||
|
Policy *Policy `json:"policy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlreadyBound returns whether this EAK is already bound to
|
// AlreadyBound returns whether this EAK is already bound to
|
||||||
|
|
|
@ -2,6 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
|
@ -130,12 +131,14 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("BEFORE EAK BINDING")
|
||||||
|
|
||||||
if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response
|
if eak != nil { // means that we have a (valid) External Account Binding key that should be bound, updated and sent in the response
|
||||||
err := eak.BindTo(acc)
|
if err := eak.BindTo(acc); err != nil {
|
||||||
if err != nil {
|
|
||||||
render.Error(w, err)
|
render.Error(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fmt.Println("AFTER EAK BINDING")
|
||||||
if err := h.db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil {
|
if err := h.db.UpdateExternalAccountKey(ctx, prov.ID, eak); err != nil {
|
||||||
render.Error(w, acme.WrapErrorISE(err, "error updating external account binding key"))
|
render.Error(w, acme.WrapErrorISE(err, "error updating external account binding key"))
|
||||||
return
|
return
|
||||||
|
|
|
@ -60,6 +60,10 @@ func (h *Handler) validateExternalAccountBinding(ctx context.Context, nar *NewAc
|
||||||
return nil, acme.NewError(acme.ErrorUnauthorizedType, "external account binding key with id '%s' was already bound to account '%s' on %s", keyID, externalAccountKey.AccountID, externalAccountKey.BoundAt)
|
return nil, acme.NewError(acme.ErrorUnauthorizedType, "external account binding key with id '%s' was already bound to account '%s' on %s", keyID, externalAccountKey.AccountID, externalAccountKey.BoundAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(externalAccountKey.KeyBytes) == 0 {
|
||||||
|
return nil, acme.NewError(acme.ErrorServerInternalType, "no key bytes") // TODO(hs): improve error message
|
||||||
|
}
|
||||||
|
|
||||||
payload, err := eabJWS.Verify(externalAccountKey.KeyBytes)
|
payload, err := eabJWS.Verify(externalAccountKey.KeyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, acme.WrapErrorISE(err, "error verifying externalAccountBinding signature")
|
return nil, acme.WrapErrorISE(err, "error verifying externalAccountBinding signature")
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -110,6 +111,9 @@ func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO(hs): gather all errors, so that we can build one response with subproblems; include the nor.Validate()
|
// TODO(hs): gather all errors, so that we can build one response with subproblems; include the nor.Validate()
|
||||||
// error here too, like in example?
|
// error here too, like in example?
|
||||||
|
|
||||||
|
eak, err := h.db.GetExternalAccountKeyByAccountID(ctx, prov.GetID(), acc.ID)
|
||||||
|
fmt.Println("EAK: ", eak, err)
|
||||||
|
|
||||||
for _, identifier := range nor.Identifiers {
|
for _, identifier := range nor.Identifiers {
|
||||||
// evaluate the provisioner level policy
|
// evaluate the provisioner level policy
|
||||||
orderIdentifier := provisioner.ACMEIdentifier{Type: provisioner.ACMEIdentifierType(identifier.Type), Value: identifier.Value}
|
orderIdentifier := provisioner.ACMEIdentifier{Type: provisioner.ACMEIdentifierType(identifier.Type), Value: identifier.Value}
|
||||||
|
|
|
@ -782,6 +782,11 @@ func TestHandler_NewOrder(t *testing.T) {
|
||||||
assert.Equals(t, ch.Value, "zap.internal")
|
assert.Equals(t, ch.Value, "zap.internal")
|
||||||
return errors.New("force")
|
return errors.New("force")
|
||||||
},
|
},
|
||||||
|
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
|
||||||
|
assert.Equals(t, prov.GetID(), provisionerID)
|
||||||
|
assert.Equals(t, "accID", accountID)
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
err: acme.NewErrorISE("error creating challenge: force"),
|
err: acme.NewErrorISE("error creating challenge: force"),
|
||||||
}
|
}
|
||||||
|
@ -852,6 +857,11 @@ func TestHandler_NewOrder(t *testing.T) {
|
||||||
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
||||||
return errors.New("force")
|
return errors.New("force")
|
||||||
},
|
},
|
||||||
|
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
|
||||||
|
assert.Equals(t, prov.GetID(), provisionerID)
|
||||||
|
assert.Equals(t, "accID", accountID)
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
err: acme.NewErrorISE("error creating order: force"),
|
err: acme.NewErrorISE("error creating order: force"),
|
||||||
}
|
}
|
||||||
|
@ -949,6 +959,11 @@ func TestHandler_NewOrder(t *testing.T) {
|
||||||
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID, *az2ID})
|
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID, *az2ID})
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
|
||||||
|
assert.Equals(t, prov.GetID(), provisionerID)
|
||||||
|
assert.Equals(t, "accID", accountID)
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
vr: func(t *testing.T, o *acme.Order) {
|
vr: func(t *testing.T, o *acme.Order) {
|
||||||
now := clock.Now()
|
now := clock.Now()
|
||||||
|
@ -1042,6 +1057,11 @@ func TestHandler_NewOrder(t *testing.T) {
|
||||||
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
|
||||||
|
assert.Equals(t, prov.GetID(), provisionerID)
|
||||||
|
assert.Equals(t, "accID", accountID)
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
vr: func(t *testing.T, o *acme.Order) {
|
vr: func(t *testing.T, o *acme.Order) {
|
||||||
now := clock.Now()
|
now := clock.Now()
|
||||||
|
@ -1135,6 +1155,11 @@ func TestHandler_NewOrder(t *testing.T) {
|
||||||
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
|
||||||
|
assert.Equals(t, prov.GetID(), provisionerID)
|
||||||
|
assert.Equals(t, "accID", accountID)
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
vr: func(t *testing.T, o *acme.Order) {
|
vr: func(t *testing.T, o *acme.Order) {
|
||||||
now := clock.Now()
|
now := clock.Now()
|
||||||
|
@ -1227,6 +1252,11 @@ func TestHandler_NewOrder(t *testing.T) {
|
||||||
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
|
||||||
|
assert.Equals(t, prov.GetID(), provisionerID)
|
||||||
|
assert.Equals(t, "accID", accountID)
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
vr: func(t *testing.T, o *acme.Order) {
|
vr: func(t *testing.T, o *acme.Order) {
|
||||||
testBufferDur := 5 * time.Second
|
testBufferDur := 5 * time.Second
|
||||||
|
@ -1320,6 +1350,11 @@ func TestHandler_NewOrder(t *testing.T) {
|
||||||
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
|
||||||
|
assert.Equals(t, prov.GetID(), provisionerID)
|
||||||
|
assert.Equals(t, "accID", accountID)
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
vr: func(t *testing.T, o *acme.Order) {
|
vr: func(t *testing.T, o *acme.Order) {
|
||||||
testBufferDur := 5 * time.Second
|
testBufferDur := 5 * time.Second
|
||||||
|
|
12
acme/db.go
12
acme/db.go
|
@ -23,6 +23,7 @@ type DB interface {
|
||||||
GetExternalAccountKey(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
|
GetExternalAccountKey(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
|
||||||
GetExternalAccountKeys(ctx context.Context, provisionerID, cursor string, limit int) ([]*ExternalAccountKey, string, error)
|
GetExternalAccountKeys(ctx context.Context, provisionerID, cursor string, limit int) ([]*ExternalAccountKey, string, error)
|
||||||
GetExternalAccountKeyByReference(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
GetExternalAccountKeyByReference(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
||||||
|
GetExternalAccountKeyByAccountID(ctx context.Context, provisionerID, accountID string) (*ExternalAccountKey, error)
|
||||||
DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID string) error
|
DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID string) error
|
||||||
UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
|
UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
|
||||||
|
|
||||||
|
@ -60,6 +61,7 @@ type MockDB struct {
|
||||||
MockGetExternalAccountKey func(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
|
MockGetExternalAccountKey func(ctx context.Context, provisionerID, keyID string) (*ExternalAccountKey, error)
|
||||||
MockGetExternalAccountKeys func(ctx context.Context, provisionerID, cursor string, limit int) ([]*ExternalAccountKey, string, error)
|
MockGetExternalAccountKeys func(ctx context.Context, provisionerID, cursor string, limit int) ([]*ExternalAccountKey, string, error)
|
||||||
MockGetExternalAccountKeyByReference func(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
MockGetExternalAccountKeyByReference func(ctx context.Context, provisionerID, reference string) (*ExternalAccountKey, error)
|
||||||
|
MockGetExternalAccountKeyByAccountID func(ctx context.Context, provisionerID, accountID string) (*ExternalAccountKey, error)
|
||||||
MockDeleteExternalAccountKey func(ctx context.Context, provisionerID, keyID string) error
|
MockDeleteExternalAccountKey func(ctx context.Context, provisionerID, keyID string) error
|
||||||
MockUpdateExternalAccountKey func(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
|
MockUpdateExternalAccountKey func(ctx context.Context, provisionerID string, eak *ExternalAccountKey) error
|
||||||
|
|
||||||
|
@ -168,6 +170,16 @@ func (m *MockDB) GetExternalAccountKeyByReference(ctx context.Context, provision
|
||||||
return m.MockRet1.(*ExternalAccountKey), m.MockError
|
return m.MockRet1.(*ExternalAccountKey), m.MockError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExternalAccountKeyByAccountID mock
|
||||||
|
func (m *MockDB) GetExternalAccountKeyByAccountID(ctx context.Context, provisionerID, accountID string) (*ExternalAccountKey, error) {
|
||||||
|
if m.MockGetExternalAccountKeyByAccountID != nil {
|
||||||
|
return m.MockGetExternalAccountKeyByAccountID(ctx, provisionerID, accountID)
|
||||||
|
} else if m.MockError != nil {
|
||||||
|
return nil, m.MockError
|
||||||
|
}
|
||||||
|
return m.MockRet1.(*ExternalAccountKey), m.MockError
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteExternalAccountKey mock
|
// DeleteExternalAccountKey mock
|
||||||
func (m *MockDB) DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID string) error {
|
func (m *MockDB) DeleteExternalAccountKey(ctx context.Context, provisionerID, keyID string) error {
|
||||||
if m.MockDeleteExternalAccountKey != nil {
|
if m.MockDeleteExternalAccountKey != nil {
|
||||||
|
|
|
@ -226,6 +226,10 @@ func (db *DB) GetExternalAccountKeyByReference(ctx context.Context, provisionerI
|
||||||
return db.GetExternalAccountKey(ctx, provisionerID, dbExternalAccountKeyReference.ExternalAccountKeyID)
|
return db.GetExternalAccountKey(ctx, provisionerID, dbExternalAccountKeyReference.ExternalAccountKeyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) GetExternalAccountKeyByAccountID(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *acme.ExternalAccountKey) error {
|
func (db *DB) UpdateExternalAccountKey(ctx context.Context, provisionerID string, eak *acme.ExternalAccountKey) error {
|
||||||
externalAccountKeyMutex.Lock()
|
externalAccountKeyMutex.Lock()
|
||||||
defer externalAccountKeyMutex.Unlock()
|
defer externalAccountKeyMutex.Unlock()
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
|
|
||||||
"google.golang.org/protobuf/encoding/protojson"
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ func (h *Handler) Route(r api.Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
acmePolicyMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
|
acmePolicyMiddleware := func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return authnz(disabledInStandalone(h.loadProvisionerByName(h.requireEABEnabled(next))))
|
return authnz(disabledInStandalone(h.loadProvisionerByName(h.requireEABEnabled(h.loadExternalAccountKey(next)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provisioners
|
// Provisioners
|
||||||
|
@ -92,8 +92,13 @@ func (h *Handler) Route(r api.Router) {
|
||||||
r.MethodFunc("DELETE", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(h.policyResponder.DeleteProvisionerPolicy))
|
r.MethodFunc("DELETE", "/provisioners/{provisionerName}/policy", provisionerPolicyMiddleware(h.policyResponder.DeleteProvisionerPolicy))
|
||||||
|
|
||||||
// Policy - ACME Account
|
// Policy - ACME Account
|
||||||
r.MethodFunc("GET", "/acme/policy/{provisionerName}/{accountID}", acmePolicyMiddleware(h.policyResponder.GetACMEAccountPolicy))
|
r.MethodFunc("GET", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(h.policyResponder.GetACMEAccountPolicy))
|
||||||
r.MethodFunc("POST", "/acme/policy/{provisionerName}/{accountID}", acmePolicyMiddleware(h.policyResponder.CreateACMEAccountPolicy))
|
r.MethodFunc("GET", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(h.policyResponder.GetACMEAccountPolicy))
|
||||||
r.MethodFunc("PUT", "/acme/policy/{provisionerName}/{accountID}", acmePolicyMiddleware(h.policyResponder.UpdateACMEAccountPolicy))
|
r.MethodFunc("POST", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(h.policyResponder.CreateACMEAccountPolicy))
|
||||||
r.MethodFunc("DELETE", "/acme/policy/{provisionerName}/{accountID}", acmePolicyMiddleware(h.policyResponder.DeleteACMEAccountPolicy))
|
r.MethodFunc("POST", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(h.policyResponder.CreateACMEAccountPolicy))
|
||||||
|
r.MethodFunc("PUT", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(h.policyResponder.UpdateACMEAccountPolicy))
|
||||||
|
r.MethodFunc("PUT", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(h.policyResponder.UpdateACMEAccountPolicy))
|
||||||
|
r.MethodFunc("DELETE", "/acme/policy/{provisionerName}/reference/{reference}", acmePolicyMiddleware(h.policyResponder.DeleteACMEAccountPolicy))
|
||||||
|
r.MethodFunc("DELETE", "/acme/policy/{provisionerName}/key/{keyID}", acmePolicyMiddleware(h.policyResponder.DeleteACMEAccountPolicy))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"go.step.sm/linkedca"
|
"go.step.sm/linkedca"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/acme"
|
||||||
"github.com/smallstep/certificates/api/render"
|
"github.com/smallstep/certificates/api/render"
|
||||||
"github.com/smallstep/certificates/authority/admin"
|
"github.com/smallstep/certificates/authority/admin"
|
||||||
"github.com/smallstep/certificates/authority/admin/db/nosql"
|
"github.com/smallstep/certificates/authority/admin/db/nosql"
|
||||||
|
@ -81,12 +83,12 @@ func (h *Handler) loadProvisionerByName(next http.HandlerFunc) http.HandlerFunc
|
||||||
func (h *Handler) checkAction(next http.HandlerFunc, supportedInStandalone bool) http.HandlerFunc {
|
func (h *Handler) checkAction(next http.HandlerFunc, supportedInStandalone bool) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// temporarily only support the admin nosql DB
|
// // temporarily only support the admin nosql DB
|
||||||
if _, ok := h.adminDB.(*nosql.DB); !ok {
|
// if _, ok := h.adminDB.(*nosql.DB); !ok {
|
||||||
render.Error(w, admin.NewError(admin.ErrorNotImplementedType,
|
// render.Error(w, admin.NewError(admin.ErrorNotImplementedType,
|
||||||
"operation not supported"))
|
// "operation not supported"))
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// actions allowed in standalone mode are always supported
|
// actions allowed in standalone mode are always supported
|
||||||
if supportedInStandalone {
|
if supportedInStandalone {
|
||||||
|
@ -106,3 +108,118 @@ func (h *Handler) checkAction(next http.HandlerFunc, supportedInStandalone bool)
|
||||||
next(w, r)
|
next(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadExternalAccountKey is a middleware that searches for an ACME
|
||||||
|
// External Account Key by accountID, keyID or reference and stores it in the context.
|
||||||
|
func (h *Handler) loadExternalAccountKey(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
prov := linkedca.ProvisionerFromContext(ctx)
|
||||||
|
|
||||||
|
reference := chi.URLParam(r, "reference")
|
||||||
|
keyID := chi.URLParam(r, "keyID")
|
||||||
|
|
||||||
|
var (
|
||||||
|
eak *acme.ExternalAccountKey
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if keyID != "" {
|
||||||
|
eak, err = h.acmeDB.GetExternalAccountKey(ctx, prov.GetId(), keyID)
|
||||||
|
} else {
|
||||||
|
eak, err = h.acmeDB.GetExternalAccountKeyByReference(ctx, prov.GetId(), reference)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// TODO: handle error; not found vs. some internal server error
|
||||||
|
render.Error(w, admin.WrapErrorISE(err, "error retrieving ACME External Account key"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if eak == nil {
|
||||||
|
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME External Account Key does not exist"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
linkedEAK := eakToLinked(eak)
|
||||||
|
|
||||||
|
ctx = linkedca.NewContextWithExternalAccountKey(ctx, linkedEAK)
|
||||||
|
|
||||||
|
next(w, r.WithContext(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func eakToLinked(k *acme.ExternalAccountKey) *linkedca.EABKey {
|
||||||
|
|
||||||
|
if k == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
eak := &linkedca.EABKey{
|
||||||
|
Id: k.ID,
|
||||||
|
HmacKey: k.KeyBytes,
|
||||||
|
Provisioner: k.ProvisionerID,
|
||||||
|
Reference: k.Reference,
|
||||||
|
Account: k.AccountID,
|
||||||
|
CreatedAt: timestamppb.New(k.CreatedAt),
|
||||||
|
BoundAt: timestamppb.New(k.BoundAt),
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Policy != nil {
|
||||||
|
eak.Policy = &linkedca.Policy{
|
||||||
|
X509: &linkedca.X509Policy{
|
||||||
|
Allow: &linkedca.X509Names{},
|
||||||
|
Deny: &linkedca.X509Names{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
eak.Policy.X509.Allow.Dns = k.Policy.X509.Allowed.DNSNames
|
||||||
|
eak.Policy.X509.Allow.Ips = k.Policy.X509.Allowed.IPRanges
|
||||||
|
eak.Policy.X509.Deny.Dns = k.Policy.X509.Denied.DNSNames
|
||||||
|
eak.Policy.X509.Deny.Ips = k.Policy.X509.Denied.IPRanges
|
||||||
|
}
|
||||||
|
|
||||||
|
return eak
|
||||||
|
}
|
||||||
|
|
||||||
|
func linkedEAKToCertificates(k *linkedca.EABKey) *acme.ExternalAccountKey {
|
||||||
|
if k == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
eak := &acme.ExternalAccountKey{
|
||||||
|
ID: k.Id,
|
||||||
|
ProvisionerID: k.Provisioner,
|
||||||
|
Reference: k.Reference,
|
||||||
|
AccountID: k.Account,
|
||||||
|
KeyBytes: k.HmacKey,
|
||||||
|
CreatedAt: k.CreatedAt.AsTime(),
|
||||||
|
BoundAt: k.BoundAt.AsTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Policy == nil {
|
||||||
|
return eak
|
||||||
|
}
|
||||||
|
|
||||||
|
eak.Policy = &acme.Policy{}
|
||||||
|
|
||||||
|
if k.Policy.X509 == nil {
|
||||||
|
return eak
|
||||||
|
}
|
||||||
|
|
||||||
|
eak.Policy.X509 = acme.X509Policy{
|
||||||
|
Allowed: acme.PolicyNames{},
|
||||||
|
Denied: acme.PolicyNames{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Policy.X509.Allow != nil {
|
||||||
|
eak.Policy.X509.Allowed.DNSNames = k.Policy.X509.Allow.Dns
|
||||||
|
eak.Policy.X509.Allowed.IPRanges = k.Policy.X509.Allow.Ips
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.Policy.X509.Deny != nil {
|
||||||
|
eak.Policy.X509.Denied.DNSNames = k.Policy.X509.Deny.Dns
|
||||||
|
eak.Policy.X509.Denied.IPRanges = k.Policy.X509.Deny.Ips
|
||||||
|
}
|
||||||
|
|
||||||
|
return eak
|
||||||
|
}
|
||||||
|
|
|
@ -368,15 +368,15 @@ func TestHandler_checkAction(t *testing.T) {
|
||||||
statusCode int
|
statusCode int
|
||||||
}
|
}
|
||||||
var tests = map[string]func(t *testing.T) test{
|
var tests = map[string]func(t *testing.T) test{
|
||||||
"standalone-mockdb-supported": func(t *testing.T) test {
|
// "standalone-mockdb-supported": func(t *testing.T) test {
|
||||||
err := admin.NewError(admin.ErrorNotImplementedType, "operation not supported")
|
// err := admin.NewError(admin.ErrorNotImplementedType, "operation not supported")
|
||||||
err.Message = "operation not supported"
|
// err.Message = "operation not supported"
|
||||||
return test{
|
// return test{
|
||||||
adminDB: &admin.MockDB{},
|
// adminDB: &admin.MockDB{},
|
||||||
statusCode: 501,
|
// statusCode: 501,
|
||||||
err: err,
|
// err: err,
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
"standalone-nosql-supported": func(t *testing.T) test {
|
"standalone-nosql-supported": func(t *testing.T) test {
|
||||||
return test{
|
return test{
|
||||||
supportedInStandalone: true,
|
supportedInStandalone: true,
|
||||||
|
@ -400,22 +400,21 @@ func TestHandler_checkAction(t *testing.T) {
|
||||||
err: err,
|
err: err,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"standalone-no-nosql-not-supported": func(t *testing.T) test {
|
// "standalone-no-nosql-not-supported": func(t *testing.T) test {
|
||||||
// TODO(hs): temporarily expects an error instead of an OK response
|
// // TODO(hs): temporarily expects an error instead of an OK response
|
||||||
err := admin.NewError(admin.ErrorNotImplementedType, "operation not supported")
|
// err := admin.NewError(admin.ErrorNotImplementedType, "operation not supported")
|
||||||
err.Message = "operation not supported"
|
// err.Message = "operation not supported"
|
||||||
return test{
|
// return test{
|
||||||
supportedInStandalone: false,
|
// supportedInStandalone: false,
|
||||||
adminDB: &admin.MockDB{},
|
// adminDB: &admin.MockDB{},
|
||||||
next: func(w http.ResponseWriter, r *http.Request) {
|
// next: func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write(nil) // mock response with status 200
|
// w.Write(nil) // mock response with status 200
|
||||||
},
|
// },
|
||||||
statusCode: 501,
|
// statusCode: 501,
|
||||||
err: err,
|
// err: err,
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, prep := range tests {
|
for name, prep := range tests {
|
||||||
tc := prep(t)
|
tc := prep(t)
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"go.step.sm/linkedca"
|
"go.step.sm/linkedca"
|
||||||
|
|
||||||
|
"github.com/smallstep/certificates/acme"
|
||||||
"github.com/smallstep/certificates/api/read"
|
"github.com/smallstep/certificates/api/read"
|
||||||
"github.com/smallstep/certificates/api/render"
|
"github.com/smallstep/certificates/api/render"
|
||||||
"github.com/smallstep/certificates/authority"
|
"github.com/smallstep/certificates/authority"
|
||||||
|
@ -31,13 +32,15 @@ type policyAdminResponderInterface interface {
|
||||||
type PolicyAdminResponder struct {
|
type PolicyAdminResponder struct {
|
||||||
auth adminAuthority
|
auth adminAuthority
|
||||||
adminDB admin.DB
|
adminDB admin.DB
|
||||||
|
acmeDB acme.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewACMEAdminResponder returns a new ACMEAdminResponder
|
// NewACMEAdminResponder returns a new ACMEAdminResponder
|
||||||
func NewPolicyAdminResponder(auth adminAuthority, adminDB admin.DB) *PolicyAdminResponder {
|
func NewPolicyAdminResponder(auth adminAuthority, adminDB admin.DB, acmeDB acme.DB) *PolicyAdminResponder {
|
||||||
return &PolicyAdminResponder{
|
return &PolicyAdminResponder{
|
||||||
auth: auth,
|
auth: auth,
|
||||||
adminDB: adminDB,
|
adminDB: adminDB,
|
||||||
|
acmeDB: acmeDB,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,8 +159,7 @@ func (par *PolicyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = par.auth.RemoveAuthorityPolicy(ctx)
|
if err := par.auth.RemoveAuthorityPolicy(ctx); err != nil {
|
||||||
if err != nil {
|
|
||||||
render.Error(w, admin.WrapErrorISE(err, "error deleting authority policy"))
|
render.Error(w, admin.WrapErrorISE(err, "error deleting authority policy"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -200,8 +202,7 @@ func (par *PolicyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter,
|
||||||
|
|
||||||
prov.Policy = newPolicy
|
prov.Policy = newPolicy
|
||||||
|
|
||||||
err := par.auth.UpdateProvisioner(ctx, prov)
|
if err := par.auth.UpdateProvisioner(ctx, prov); err != nil {
|
||||||
if err != nil {
|
|
||||||
var pe *authority.PolicyError
|
var pe *authority.PolicyError
|
||||||
isPolicyError := errors.As(err, &pe)
|
isPolicyError := errors.As(err, &pe)
|
||||||
if isPolicyError && pe.Typ == authority.AdminLockOut || pe.Typ == authority.EvaluationFailure || pe.Typ == authority.ConfigurationFailure {
|
if isPolicyError && pe.Typ == authority.AdminLockOut || pe.Typ == authority.EvaluationFailure || pe.Typ == authority.ConfigurationFailure {
|
||||||
|
@ -233,8 +234,7 @@ func (par *PolicyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter,
|
||||||
}
|
}
|
||||||
|
|
||||||
prov.Policy = newPolicy
|
prov.Policy = newPolicy
|
||||||
err := par.auth.UpdateProvisioner(ctx, prov)
|
if err := par.auth.UpdateProvisioner(ctx, prov); err != nil {
|
||||||
if err != nil {
|
|
||||||
var pe *authority.PolicyError
|
var pe *authority.PolicyError
|
||||||
isPolicyError := errors.As(err, &pe)
|
isPolicyError := errors.As(err, &pe)
|
||||||
if isPolicyError && pe.Typ == authority.AdminLockOut || pe.Typ == authority.EvaluationFailure || pe.Typ == authority.ConfigurationFailure {
|
if isPolicyError && pe.Typ == authority.AdminLockOut || pe.Typ == authority.EvaluationFailure || pe.Typ == authority.ConfigurationFailure {
|
||||||
|
@ -263,8 +263,7 @@ func (par *PolicyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter,
|
||||||
// remove the policy
|
// remove the policy
|
||||||
prov.Policy = nil
|
prov.Policy = nil
|
||||||
|
|
||||||
err := par.auth.UpdateProvisioner(ctx, prov)
|
if err := par.auth.UpdateProvisioner(ctx, prov); err != nil {
|
||||||
if err != nil {
|
|
||||||
render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner policy"))
|
render.Error(w, admin.WrapErrorISE(err, "error deleting provisioner policy"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -273,17 +272,92 @@ func (par *PolicyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (par *PolicyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
|
func (par *PolicyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
render.JSONStatus(w, "not implemented yet", http.StatusNotImplemented)
|
ctx := r.Context()
|
||||||
|
eak := linkedca.ExternalAccountKeyFromContext(ctx)
|
||||||
|
|
||||||
|
policy := eak.GetPolicy()
|
||||||
|
if policy == nil {
|
||||||
|
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.ProtoJSONStatus(w, policy, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (par *PolicyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
|
func (par *PolicyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
render.JSONStatus(w, "not implemented yet", http.StatusNotImplemented)
|
ctx := r.Context()
|
||||||
|
prov := linkedca.ProvisionerFromContext(ctx)
|
||||||
|
eak := linkedca.ExternalAccountKeyFromContext(ctx)
|
||||||
|
|
||||||
|
policy := eak.GetPolicy()
|
||||||
|
if policy != nil {
|
||||||
|
adminErr := admin.NewError(admin.ErrorBadRequestType, "ACME EAK %s already has a policy", eak.Id)
|
||||||
|
adminErr.Status = http.StatusConflict
|
||||||
|
render.Error(w, adminErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newPolicy = new(linkedca.Policy)
|
||||||
|
if !read.ProtoJSONWithCheck(w, r.Body, newPolicy) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eak.Policy = newPolicy
|
||||||
|
|
||||||
|
acmeEAK := linkedEAKToCertificates(eak)
|
||||||
|
if err := par.acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
|
||||||
|
render.Error(w, admin.WrapErrorISE(err, "error creating ACME EAK policy"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.ProtoJSONStatus(w, newPolicy, http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (par *PolicyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
|
func (par *PolicyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
render.JSONStatus(w, "not implemented yet", http.StatusNotImplemented)
|
ctx := r.Context()
|
||||||
|
prov := linkedca.ProvisionerFromContext(ctx)
|
||||||
|
eak := linkedca.ExternalAccountKeyFromContext(ctx)
|
||||||
|
|
||||||
|
policy := eak.GetPolicy()
|
||||||
|
if policy == nil {
|
||||||
|
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newPolicy = new(linkedca.Policy)
|
||||||
|
if !read.ProtoJSONWithCheck(w, r.Body, newPolicy) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eak.Policy = newPolicy
|
||||||
|
acmeEAK := linkedEAKToCertificates(eak)
|
||||||
|
if err := par.acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
|
||||||
|
render.Error(w, admin.WrapErrorISE(err, "error updating ACME EAK policy"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.ProtoJSONStatus(w, newPolicy, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (par *PolicyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
|
func (par *PolicyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, r *http.Request) {
|
||||||
render.JSONStatus(w, "not implemented yet", http.StatusNotImplemented)
|
ctx := r.Context()
|
||||||
|
prov := linkedca.ProvisionerFromContext(ctx)
|
||||||
|
eak := linkedca.ExternalAccountKeyFromContext(ctx)
|
||||||
|
|
||||||
|
policy := eak.GetPolicy()
|
||||||
|
if policy == nil {
|
||||||
|
render.Error(w, admin.NewError(admin.ErrorNotFoundType, "ACME EAK policy does not exist"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the policy
|
||||||
|
eak.Policy = nil
|
||||||
|
|
||||||
|
acmeEAK := linkedEAKToCertificates(eak)
|
||||||
|
if err := par.acmeDB.UpdateExternalAccountKey(ctx, prov.GetId(), acmeEAK); err != nil {
|
||||||
|
render.Error(w, admin.WrapErrorISE(err, "error deleting ACME EAK policy"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
|
@ -808,6 +808,290 @@ retry:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *AdminClient) GetProvisionerPolicy(provisionerName string) (*linkedca.Policy, error) {
|
||||||
|
var retried bool
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioner", provisionerName, "policy")})
|
||||||
|
tok, err := c.generateAdminToken(u.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error generating admin token: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodGet, u.String(), http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating GET %s request failed: %w", u, err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", tok)
|
||||||
|
retry:
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("client GET %s failed: %w", u, err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
if !retried && c.retryOnError(resp) {
|
||||||
|
retried = true
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
return nil, readAdminError(resp.Body)
|
||||||
|
}
|
||||||
|
var policy = new(linkedca.Policy)
|
||||||
|
if err := readProtoJSON(resp.Body, policy); err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading %s: %w", u, err)
|
||||||
|
}
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AdminClient) CreateProvisionerPolicy(provisionerName string, p *linkedca.Policy) (*linkedca.Policy, error) {
|
||||||
|
var retried bool
|
||||||
|
body, err := protojson.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshaling request: %w", err)
|
||||||
|
}
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioner", provisionerName, "policy")})
|
||||||
|
tok, err := c.generateAdminToken(u.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error generating admin token: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating POST %s request failed: %w", u, err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", tok)
|
||||||
|
retry:
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("client POST %s failed: %w", u, err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
if !retried && c.retryOnError(resp) {
|
||||||
|
retried = true
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
return nil, readAdminError(resp.Body)
|
||||||
|
}
|
||||||
|
var policy = new(linkedca.Policy)
|
||||||
|
if err := readProtoJSON(resp.Body, policy); err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading %s: %w", u, err)
|
||||||
|
}
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AdminClient) UpdateProvisionerPolicy(provisionerName string, p *linkedca.Policy) (*linkedca.Policy, error) {
|
||||||
|
var retried bool
|
||||||
|
body, err := protojson.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshaling request: %w", err)
|
||||||
|
}
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioner", provisionerName, "policy")})
|
||||||
|
tok, err := c.generateAdminToken(u.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error generating admin token: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPut, u.String(), bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating PUT %s request failed: %w", u, err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", tok)
|
||||||
|
retry:
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("client PUT %s failed: %w", u, err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
if !retried && c.retryOnError(resp) {
|
||||||
|
retried = true
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
return nil, readAdminError(resp.Body)
|
||||||
|
}
|
||||||
|
var policy = new(linkedca.Policy)
|
||||||
|
if err := readProtoJSON(resp.Body, policy); err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading %s: %w", u, err)
|
||||||
|
}
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AdminClient) RemoveProvisionerPolicy(provisionerName string) error {
|
||||||
|
var retried bool
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioner", provisionerName, "policy")})
|
||||||
|
tok, err := c.generateAdminToken(u.Path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error generating admin token: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodDelete, u.String(), http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating DELETE %s request failed: %w", u, err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", tok)
|
||||||
|
retry:
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("client DELETE %s failed: %w", u, err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
if !retried && c.retryOnError(resp) {
|
||||||
|
retried = true
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
return readAdminError(resp.Body)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AdminClient) GetACMEPolicy(provisionerName, reference, keyID string) (*linkedca.Policy, error) {
|
||||||
|
var retried bool
|
||||||
|
var urlPath string
|
||||||
|
switch {
|
||||||
|
case keyID != "":
|
||||||
|
urlPath = path.Join(adminURLPrefix, "acme", "policy", provisionerName, "key", keyID)
|
||||||
|
default:
|
||||||
|
urlPath = path.Join(adminURLPrefix, "acme", "policy", provisionerName, "reference", reference)
|
||||||
|
}
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: urlPath})
|
||||||
|
tok, err := c.generateAdminToken(u.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error generating admin token: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodGet, u.String(), http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating GET %s request failed: %w", u, err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", tok)
|
||||||
|
retry:
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("client GET %s failed: %w", u, err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
if !retried && c.retryOnError(resp) {
|
||||||
|
retried = true
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
return nil, readAdminError(resp.Body)
|
||||||
|
}
|
||||||
|
var policy = new(linkedca.Policy)
|
||||||
|
if err := readProtoJSON(resp.Body, policy); err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading %s: %w", u, err)
|
||||||
|
}
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AdminClient) CreateACMEPolicy(provisionerName, reference, keyID string, p *linkedca.Policy) (*linkedca.Policy, error) {
|
||||||
|
var retried bool
|
||||||
|
body, err := protojson.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshaling request: %w", err)
|
||||||
|
}
|
||||||
|
var urlPath string
|
||||||
|
switch {
|
||||||
|
case keyID != "":
|
||||||
|
urlPath = path.Join(adminURLPrefix, "acme", "policy", provisionerName, "key", keyID)
|
||||||
|
default:
|
||||||
|
urlPath = path.Join(adminURLPrefix, "acme", "policy", provisionerName, "reference", reference)
|
||||||
|
}
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: urlPath})
|
||||||
|
tok, err := c.generateAdminToken(u.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error generating admin token: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating POST %s request failed: %w", u, err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", tok)
|
||||||
|
retry:
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("client POST %s failed: %w", u, err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
if !retried && c.retryOnError(resp) {
|
||||||
|
retried = true
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
return nil, readAdminError(resp.Body)
|
||||||
|
}
|
||||||
|
var policy = new(linkedca.Policy)
|
||||||
|
if err := readProtoJSON(resp.Body, policy); err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading %s: %w", u, err)
|
||||||
|
}
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AdminClient) UpdateACMEPolicy(provisionerName, reference, keyID string, p *linkedca.Policy) (*linkedca.Policy, error) {
|
||||||
|
var retried bool
|
||||||
|
body, err := protojson.Marshal(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshaling request: %w", err)
|
||||||
|
}
|
||||||
|
var urlPath string
|
||||||
|
switch {
|
||||||
|
case keyID != "":
|
||||||
|
urlPath = path.Join(adminURLPrefix, "acme", "policy", provisionerName, "key", keyID)
|
||||||
|
default:
|
||||||
|
urlPath = path.Join(adminURLPrefix, "acme", "policy", provisionerName, "reference", reference)
|
||||||
|
}
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: urlPath})
|
||||||
|
tok, err := c.generateAdminToken(u.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error generating admin token: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPut, u.String(), bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("creating PUT %s request failed: %w", u, err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", tok)
|
||||||
|
retry:
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("client PUT %s failed: %w", u, err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
if !retried && c.retryOnError(resp) {
|
||||||
|
retried = true
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
return nil, readAdminError(resp.Body)
|
||||||
|
}
|
||||||
|
var policy = new(linkedca.Policy)
|
||||||
|
if err := readProtoJSON(resp.Body, policy); err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading %s: %w", u, err)
|
||||||
|
}
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AdminClient) RemoveACMEPolicy(provisionerName, reference, keyID string) error {
|
||||||
|
var retried bool
|
||||||
|
var urlPath string
|
||||||
|
switch {
|
||||||
|
case keyID != "":
|
||||||
|
urlPath = path.Join(adminURLPrefix, "acme", "policy", provisionerName, "key", keyID)
|
||||||
|
default:
|
||||||
|
urlPath = path.Join(adminURLPrefix, "acme", "policy", provisionerName, "reference", reference)
|
||||||
|
}
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: urlPath})
|
||||||
|
tok, err := c.generateAdminToken(u.Path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error generating admin token: %w", err)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodDelete, u.String(), http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating DELETE %s request failed: %w", u, err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", tok)
|
||||||
|
retry:
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("client DELETE %s failed: %w", u, err)
|
||||||
|
}
|
||||||
|
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 {
|
func readAdminError(r io.ReadCloser) error {
|
||||||
// TODO: not all errors can be read (i.e. 404); seems to be a bigger issue
|
// TODO: not all errors can be read (i.e. 404); seems to be a bigger issue
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
2
ca/ca.go
2
ca/ca.go
|
@ -208,7 +208,7 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
|
||||||
adminDB := auth.GetAdminDatabase()
|
adminDB := auth.GetAdminDatabase()
|
||||||
if adminDB != nil {
|
if adminDB != nil {
|
||||||
acmeAdminResponder := adminAPI.NewACMEAdminResponder()
|
acmeAdminResponder := adminAPI.NewACMEAdminResponder()
|
||||||
policyAdminResponder := adminAPI.NewPolicyAdminResponder(auth, adminDB)
|
policyAdminResponder := adminAPI.NewPolicyAdminResponder(auth, adminDB, acmeDB)
|
||||||
adminHandler := adminAPI.NewHandler(auth, adminDB, acmeDB, acmeAdminResponder, policyAdminResponder)
|
adminHandler := adminAPI.NewHandler(auth, adminDB, acmeDB, acmeAdminResponder, policyAdminResponder)
|
||||||
mux.Route("/admin", func(r chi.Router) {
|
mux.Route("/admin", func(r chi.Router) {
|
||||||
adminHandler.Route(r)
|
adminHandler.Route(r)
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -38,12 +38,12 @@ require (
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
||||||
go.step.sm/cli-utils v0.7.0
|
go.step.sm/cli-utils v0.7.0
|
||||||
go.step.sm/crypto v0.16.1
|
go.step.sm/crypto v0.16.1
|
||||||
go.step.sm/linkedca v0.12.1-0.20220331143637-69bee7065785
|
go.step.sm/linkedca v0.12.1-0.20220405095509-878e3e5f78a3
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de
|
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
|
||||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect
|
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 // indirect
|
||||||
google.golang.org/api v0.70.0
|
google.golang.org/api v0.70.0
|
||||||
google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7
|
google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de
|
||||||
google.golang.org/grpc v1.45.0
|
google.golang.org/grpc v1.45.0
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -717,6 +717,8 @@ go.step.sm/linkedca v0.12.0 h1:FA18uJO5P6W2pklcezMs+w+N3dVbpKEE1LP9HLsJgg4=
|
||||||
go.step.sm/linkedca v0.12.0/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
go.step.sm/linkedca v0.12.0/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
||||||
go.step.sm/linkedca v0.12.1-0.20220331143637-69bee7065785 h1:14HYoAd9P7DNpf8OkXq4OWTzEq5E6iX4hNkYu/NH4Wo=
|
go.step.sm/linkedca v0.12.1-0.20220331143637-69bee7065785 h1:14HYoAd9P7DNpf8OkXq4OWTzEq5E6iX4hNkYu/NH4Wo=
|
||||||
go.step.sm/linkedca v0.12.1-0.20220331143637-69bee7065785/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
go.step.sm/linkedca v0.12.1-0.20220331143637-69bee7065785/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
||||||
|
go.step.sm/linkedca v0.12.1-0.20220405095509-878e3e5f78a3 h1:CIq0rMhfcV3oDRT0h4de2GVpRQnBnLJTTVIdc0eFjUg=
|
||||||
|
go.step.sm/linkedca v0.12.1-0.20220405095509-878e3e5f78a3/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
||||||
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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
@ -839,6 +841,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacp
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
|
golang.org/x/net v0.0.0-20220325170049-de3da57026de h1:pZB1TWnKi+o4bENlbzAgLrEbY4RMYmUIRobMcSmfeYc=
|
||||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
|
||||||
|
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -954,6 +958,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIj
|
||||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY=
|
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY=
|
||||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 h1:D1v9ucDTYBtbz5vNuBbAhIMAGhQhJ6Ym5ah3maMVNX4=
|
||||||
|
golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
|
@ -1156,6 +1162,8 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf h1:SVYXkUz2yZS9FWb
|
||||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||||
google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7 h1:HOL66YCI20JvN2hVk6o2YIp9i/3RvzVUz82PqNr7fXw=
|
google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7 h1:HOL66YCI20JvN2hVk6o2YIp9i/3RvzVUz82PqNr7fXw=
|
||||||
google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
|
google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de h1:9Ti5SG2U4cAcluryUo/sFay3TQKoxiFMfaT0pbizU7k=
|
||||||
|
google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||||
|
|
Loading…
Reference in a new issue