From 81b0c6c37c6129930ffb659cb8758952a7834e91 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Tue, 15 Mar 2022 15:51:45 +0100 Subject: [PATCH] Add API implementation for authority and provisioner policy --- api/utils.go | 7 + authority/admin/api/admin.go | 4 + authority/admin/api/admin_test.go | 21 ++ authority/admin/api/handler.go | 52 +++- authority/admin/api/middleware.go | 23 ++ authority/admin/api/policy.go | 313 ++++++++++++++++++++++++ authority/admin/db.go | 37 +++ authority/admin/db/nosql/nosql.go | 7 +- authority/admin/db/nosql/policy.go | 144 +++++++++++ authority/admin/db/nosql/provisioner.go | 4 + authority/authority.go | 61 +++-- authority/linkedca.go | 20 ++ authority/policy.go | 132 ++++++++++ authority/policy/options.go | 34 ++- authority/provisioners.go | 53 ++++ authority/tls.go | 5 +- ca/ca.go | 3 +- go.mod | 2 +- go.sum | 4 - 19 files changed, 883 insertions(+), 43 deletions(-) create mode 100644 authority/admin/api/policy.go create mode 100644 authority/admin/db/nosql/policy.go create mode 100644 authority/policy.go diff --git a/api/utils.go b/api/utils.go index a7f4bf58..b6ff7960 100644 --- a/api/utils.go +++ b/api/utils.go @@ -66,6 +66,13 @@ func JSONStatus(w http.ResponseWriter, v interface{}, status int) { LogEnabledResponse(w, v) } +// JSONNotFound writes a HTTP Not Found response with empty body. +func JSONNotFound(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + LogEnabledResponse(w, nil) +} + // ProtoJSON writes the passed value into the http.ResponseWriter. func ProtoJSON(w http.ResponseWriter, m proto.Message) { ProtoJSONStatus(w, m, http.StatusOK) diff --git a/authority/admin/api/admin.go b/authority/admin/api/admin.go index 7aa66d0f..dd40784b 100644 --- a/authority/admin/api/admin.go +++ b/authority/admin/api/admin.go @@ -25,6 +25,10 @@ type adminAuthority interface { LoadProvisionerByID(id string) (provisioner.Interface, error) UpdateProvisioner(ctx context.Context, nu *linkedca.Provisioner) error RemoveProvisioner(ctx context.Context, id string) error + GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error) + StoreAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error + UpdateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error + RemoveAuthorityPolicy(ctx context.Context) error } // CreateAdminRequest represents the body for a CreateAdmin request. diff --git a/authority/admin/api/admin_test.go b/authority/admin/api/admin_test.go index 8d223b52..f1698139 100644 --- a/authority/admin/api/admin_test.go +++ b/authority/admin/api/admin_test.go @@ -37,6 +37,11 @@ type mockAdminAuthority struct { MockLoadProvisionerByID func(id string) (provisioner.Interface, error) MockUpdateProvisioner func(ctx context.Context, nu *linkedca.Provisioner) error MockRemoveProvisioner func(ctx context.Context, id string) error + + MockGetAuthorityPolicy func(ctx context.Context) (*linkedca.Policy, error) + MockStoreAuthorityPolicy func(ctx context.Context, policy *linkedca.Policy) error + MockUpdateAuthorityPolicy func(ctx context.Context, policy *linkedca.Policy) error + MockRemoveAuthorityPolicy func(ctx context.Context) error } func (m *mockAdminAuthority) IsAdminAPIEnabled() bool { @@ -130,6 +135,22 @@ func (m *mockAdminAuthority) RemoveProvisioner(ctx context.Context, id string) e return m.MockErr } +func (m *mockAdminAuthority) GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error) { + return nil, errors.New("not implemented yet") +} + +func (m *mockAdminAuthority) StoreAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + return errors.New("not implemented yet") +} + +func (m *mockAdminAuthority) UpdateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + return errors.New("not implemented yet") +} + +func (m *mockAdminAuthority) RemoveAuthorityPolicy(ctx context.Context) error { + return errors.New("not implemented yet") +} + func TestCreateAdminRequest_Validate(t *testing.T) { type fields struct { Subject string diff --git a/authority/admin/api/handler.go b/authority/admin/api/handler.go index 99e74c88..e59b95e0 100644 --- a/authority/admin/api/handler.go +++ b/authority/admin/api/handler.go @@ -8,32 +8,44 @@ import ( // Handler is the Admin API request handler. type Handler struct { - adminDB admin.DB - auth adminAuthority - acmeDB acme.DB - acmeResponder acmeAdminResponderInterface + adminDB admin.DB + auth adminAuthority + acmeDB acme.DB + acmeResponder acmeAdminResponderInterface + policyResponder policyAdminResponderInterface } // NewHandler returns a new Authority Config Handler. -func NewHandler(auth adminAuthority, adminDB admin.DB, acmeDB acme.DB, acmeResponder acmeAdminResponderInterface) api.RouterHandler { +func NewHandler(auth adminAuthority, adminDB admin.DB, acmeDB acme.DB, acmeResponder acmeAdminResponderInterface, policyResponder policyAdminResponderInterface) api.RouterHandler { return &Handler{ - auth: auth, - adminDB: adminDB, - acmeDB: acmeDB, - acmeResponder: acmeResponder, + auth: auth, + adminDB: adminDB, + acmeDB: acmeDB, + acmeResponder: acmeResponder, + policyResponder: policyResponder, } } // 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)) + //return h.extractAuthorizeTokenAdmin(h.requireAPIEnabled(next)) + return h.requireAPIEnabled(next) // TODO(hs): remove this; temporarily no auth checks for simple testing... } requireEABEnabled := func(next nextHTTP) nextHTTP { return h.requireEABEnabled(next) } + enabledInStandalone := func(next nextHTTP) nextHTTP { + return h.checkAction(next, true) + } + + disabledInStandalone := func(next nextHTTP) nextHTTP { + return h.checkAction(next, false) + } + // Provisioners r.MethodFunc("GET", "/provisioners/{name}", authnz(h.GetProvisioner)) r.MethodFunc("GET", "/provisioners", authnz(h.GetProvisioners)) @@ -53,4 +65,24 @@ func (h *Handler) Route(r api.Router) { r.MethodFunc("GET", "/acme/eab/{provisionerName}", authnz(requireEABEnabled(h.acmeResponder.GetExternalAccountKeys))) r.MethodFunc("POST", "/acme/eab/{provisionerName}", authnz(requireEABEnabled(h.acmeResponder.CreateExternalAccountKey))) r.MethodFunc("DELETE", "/acme/eab/{provisionerName}/{id}", authnz(requireEABEnabled(h.acmeResponder.DeleteExternalAccountKey))) + + // Policy - Authority + r.MethodFunc("GET", "/policy", authnz(enabledInStandalone(h.policyResponder.GetAuthorityPolicy))) + r.MethodFunc("POST", "/policy", authnz(enabledInStandalone(h.policyResponder.CreateAuthorityPolicy))) + r.MethodFunc("PUT", "/policy", authnz(enabledInStandalone(h.policyResponder.UpdateAuthorityPolicy))) + r.MethodFunc("DELETE", "/policy", authnz(enabledInStandalone(h.policyResponder.DeleteAuthorityPolicy))) + + // Policy - Provisioner + //r.MethodFunc("GET", "/provisioners/{name}/policy", noauth(h.policyResponder.GetProvisionerPolicy)) + r.MethodFunc("GET", "/provisioners/{name}/policy", authnz(disabledInStandalone(h.policyResponder.GetProvisionerPolicy))) + r.MethodFunc("POST", "/provisioners/{name}/policy", authnz(disabledInStandalone(h.policyResponder.CreateProvisionerPolicy))) + r.MethodFunc("PUT", "/provisioners/{name}/policy", authnz(disabledInStandalone(h.policyResponder.UpdateProvisionerPolicy))) + r.MethodFunc("DELETE", "/provisioners/{name}/policy", authnz(disabledInStandalone(h.policyResponder.DeleteProvisionerPolicy))) + + // Policy - ACME Account + // TODO: ensure we don't clash with eab; might want to change eab paths slightly (as long as we don't have it released completely; needs changes in adminClient too) + r.MethodFunc("GET", "/acme/{provisionerName}/{accountID}/policy", authnz(disabledInStandalone(h.policyResponder.GetACMEAccountPolicy))) + r.MethodFunc("POST", "/acme/{provisionerName}/{accountID}/policy", authnz(disabledInStandalone(h.policyResponder.CreateACMEAccountPolicy))) + r.MethodFunc("PUT", "/acme/{provisionerName}/{accountID}/policy", authnz(disabledInStandalone(h.policyResponder.UpdateACMEAccountPolicy))) + r.MethodFunc("DELETE", "/acme/{provisionerName}/{accountID}/policy", authnz(disabledInStandalone(h.policyResponder.DeleteACMEAccountPolicy))) } diff --git a/authority/admin/api/middleware.go b/authority/admin/api/middleware.go index 19025a9d..62aefdc3 100644 --- a/authority/admin/api/middleware.go +++ b/authority/admin/api/middleware.go @@ -6,6 +6,7 @@ import ( "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/authority/admin/db/nosql" ) type nextHTTP = func(http.ResponseWriter, *http.Request) @@ -44,6 +45,28 @@ func (h *Handler) extractAuthorizeTokenAdmin(next nextHTTP) nextHTTP { } } +// checkAction checks if an action is supported in standalone or not +func (h *Handler) checkAction(next nextHTTP, supportedInStandalone bool) nextHTTP { + return func(w http.ResponseWriter, r *http.Request) { + + // actions allowed in standalone mode are always allowed + if supportedInStandalone { + next(w, r) + return + } + + // when in standalone mode, actions are not supported + if _, ok := h.adminDB.(*nosql.DB); ok { + api.WriteError(w, admin.NewError(admin.ErrorNotImplementedType, + "operation not supported in standalone mode")) + return + } + + // continue to next http handler + next(w, r) + } +} + // ContextKey is the key type for storing and searching for ACME request // essentials in the context of a request. type ContextKey string diff --git a/authority/admin/api/policy.go b/authority/admin/api/policy.go new file mode 100644 index 00000000..c318e5e5 --- /dev/null +++ b/authority/admin/api/policy.go @@ -0,0 +1,313 @@ +package api + +import ( + "net/http" + + "github.com/go-chi/chi" + "github.com/smallstep/certificates/api" + "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/authority/provisioner" + "go.step.sm/linkedca" +) + +type policyAdminResponderInterface interface { + GetAuthorityPolicy(w http.ResponseWriter, r *http.Request) + CreateAuthorityPolicy(w http.ResponseWriter, r *http.Request) + UpdateAuthorityPolicy(w http.ResponseWriter, r *http.Request) + DeleteAuthorityPolicy(w http.ResponseWriter, r *http.Request) + GetProvisionerPolicy(w http.ResponseWriter, r *http.Request) + CreateProvisionerPolicy(w http.ResponseWriter, r *http.Request) + UpdateProvisionerPolicy(w http.ResponseWriter, r *http.Request) + DeleteProvisionerPolicy(w http.ResponseWriter, r *http.Request) + GetACMEAccountPolicy(w http.ResponseWriter, r *http.Request) + CreateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) + UpdateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) + DeleteACMEAccountPolicy(w http.ResponseWriter, r *http.Request) +} + +// PolicyAdminResponder is responsible for writing ACME admin responses +type PolicyAdminResponder struct { + auth adminAuthority + adminDB admin.DB +} + +// NewACMEAdminResponder returns a new ACMEAdminResponder +func NewPolicyAdminResponder(auth adminAuthority, adminDB admin.DB) *PolicyAdminResponder { + return &PolicyAdminResponder{ + auth: auth, + adminDB: adminDB, + } +} + +// GetAuthorityPolicy handles the GET /admin/authority/policy request +func (par *PolicyAdminResponder) GetAuthorityPolicy(w http.ResponseWriter, r *http.Request) { + + policy, err := par.auth.GetAuthorityPolicy(r.Context()) + if ae, ok := err.(*admin.Error); ok { + if !ae.IsType(admin.ErrorNotFoundType) { + api.WriteError(w, admin.WrapErrorISE(ae, "error retrieving authority policy")) + return + } + } + + if policy == nil { + api.JSONNotFound(w) + return + } + + api.ProtoJSONStatus(w, policy, http.StatusOK) +} + +// CreateAuthorityPolicy handles the POST /admin/authority/policy request +func (par *PolicyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + policy, err := par.auth.GetAuthorityPolicy(ctx) + + shouldWriteError := false + if ae, ok := err.(*admin.Error); ok { + shouldWriteError = !ae.IsType(admin.ErrorNotFoundType) + } + + if shouldWriteError { + api.WriteError(w, admin.WrapErrorISE(err, "error retrieving authority policy")) + return + } + + if policy != nil { + adminErr := admin.NewError(admin.ErrorBadRequestType, "authority already has a policy") + adminErr.Status = http.StatusConflict + api.WriteError(w, adminErr) + return + } + + var newPolicy = new(linkedca.Policy) + if err := api.ReadProtoJSON(r.Body, newPolicy); err != nil { + api.WriteError(w, err) + return + } + + if err := par.auth.StoreAuthorityPolicy(ctx, newPolicy); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error storing authority policy")) + return + } + + storedPolicy, err := par.auth.GetAuthorityPolicy(ctx) + if err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error retrieving authority policy after updating")) + return + } + + api.JSONStatus(w, storedPolicy, http.StatusCreated) +} + +// UpdateAuthorityPolicy handles the PUT /admin/authority/policy request +func (par *PolicyAdminResponder) UpdateAuthorityPolicy(w http.ResponseWriter, r *http.Request) { + var policy = new(linkedca.Policy) + if err := api.ReadProtoJSON(r.Body, policy); err != nil { + api.WriteError(w, err) + return + } + + ctx := r.Context() + if err := par.auth.UpdateAuthorityPolicy(ctx, policy); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error updating authority policy")) + return + } + + newPolicy, err := par.auth.GetAuthorityPolicy(ctx) + if err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error retrieving authority policy after updating")) + return + } + + api.ProtoJSONStatus(w, newPolicy, http.StatusOK) +} + +// DeleteAuthorityPolicy handles the DELETE /admin/authority/policy request +func (par *PolicyAdminResponder) DeleteAuthorityPolicy(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + policy, err := par.auth.GetAuthorityPolicy(ctx) + + if ae, ok := err.(*admin.Error); ok { + if !ae.IsType(admin.ErrorNotFoundType) { + api.WriteError(w, admin.WrapErrorISE(ae, "error retrieving authority policy")) + return + } + } + + if policy == nil { + api.JSONNotFound(w) + return + } + + err = par.auth.RemoveAuthorityPolicy(ctx) + if err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error deleting authority policy")) + return + } + + api.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) +} + +// GetProvisionerPolicy handles the GET /admin/provisioners/{name}/policy request +func (par *PolicyAdminResponder) GetProvisionerPolicy(w http.ResponseWriter, r *http.Request) { + // TODO: move getting provisioner to middleware? + ctx := r.Context() + name := chi.URLParam(r, "name") + var ( + p provisioner.Interface + err error + ) + if p, err = par.auth.LoadProvisionerByName(name); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) + return + } + + prov, err := par.adminDB.GetProvisioner(ctx, p.GetID()) + if err != nil { + api.WriteError(w, err) + return + } + + policy := prov.GetPolicy() + if policy == nil { + api.JSONNotFound(w) + return + } + + api.ProtoJSONStatus(w, policy, http.StatusOK) +} + +// CreateProvisionerPolicy handles the POST /admin/provisioners/{name}/policy request +func (par *PolicyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + name := chi.URLParam(r, "name") + var ( + p provisioner.Interface + err error + ) + if p, err = par.auth.LoadProvisionerByName(name); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) + return + } + + prov, err := par.adminDB.GetProvisioner(ctx, p.GetID()) + if err != nil { + api.WriteError(w, err) + return + } + + policy := prov.GetPolicy() + if policy != nil { + adminErr := admin.NewError(admin.ErrorBadRequestType, "provisioner %s already has a policy", name) + adminErr.Status = http.StatusConflict + api.WriteError(w, adminErr) + } + + var newPolicy = new(linkedca.Policy) + if err := api.ReadProtoJSON(r.Body, newPolicy); err != nil { + api.WriteError(w, err) + return + } + + prov.Policy = newPolicy + + err = par.auth.UpdateProvisioner(ctx, prov) + if err != nil { + api.WriteError(w, err) + return + } + + api.ProtoJSONStatus(w, newPolicy, http.StatusCreated) +} + +// UpdateProvisionerPolicy handles the PUT /admin/provisioners/{name}/policy request +func (par *PolicyAdminResponder) UpdateProvisionerPolicy(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + name := chi.URLParam(r, "name") + var ( + p provisioner.Interface + err error + ) + if p, err = par.auth.LoadProvisionerByName(name); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) + return + } + + prov, err := par.adminDB.GetProvisioner(ctx, p.GetID()) + if err != nil { + api.WriteError(w, err) + return + } + + var policy = new(linkedca.Policy) + if err := api.ReadProtoJSON(r.Body, policy); err != nil { + api.WriteError(w, err) + return + } + + prov.Policy = policy + err = par.auth.UpdateProvisioner(ctx, prov) + if err != nil { + api.WriteError(w, err) + return + } + + api.ProtoJSONStatus(w, policy, http.StatusOK) +} + +// DeleteProvisionerPolicy ... +func (par *PolicyAdminResponder) DeleteProvisionerPolicy(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + name := chi.URLParam(r, "name") + var ( + p provisioner.Interface + err error + ) + if p, err = par.auth.LoadProvisionerByName(name); err != nil { + api.WriteError(w, admin.WrapErrorISE(err, "error loading provisioner %s", name)) + return + } + + prov, err := par.adminDB.GetProvisioner(ctx, p.GetID()) + if err != nil { + api.WriteError(w, err) + return + } + + if prov.Policy == nil { + api.JSONNotFound(w) + return + } + + // remove the policy + prov.Policy = nil + + err = par.auth.UpdateProvisioner(ctx, prov) + if err != nil { + api.WriteError(w, err) + return + } + + api.JSON(w, &DeleteResponse{Status: "ok"}) +} + +// GetACMEAccountPolicy ... +func (par *PolicyAdminResponder) GetACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { + api.JSON(w, "ok") +} + +func (par *PolicyAdminResponder) CreateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { + api.JSON(w, "ok") +} + +func (par *PolicyAdminResponder) UpdateACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { + api.JSON(w, "ok") +} + +func (par *PolicyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, r *http.Request) { + api.JSON(w, "ok") +} diff --git a/authority/admin/db.go b/authority/admin/db.go index bf34a3c2..75ac1368 100644 --- a/authority/admin/db.go +++ b/authority/admin/db.go @@ -69,6 +69,11 @@ type DB interface { GetAdmins(ctx context.Context) ([]*linkedca.Admin, error) UpdateAdmin(ctx context.Context, admin *linkedca.Admin) error DeleteAdmin(ctx context.Context, id string) error + + CreateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error + GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error) + UpdateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error + DeleteAuthorityPolicy(ctx context.Context) error } // MockDB is an implementation of the DB interface that should only be used as @@ -86,6 +91,11 @@ type MockDB struct { MockUpdateAdmin func(ctx context.Context, adm *linkedca.Admin) error MockDeleteAdmin func(ctx context.Context, id string) error + MockCreateAuthorityPolicy func(ctx context.Context, policy *linkedca.Policy) error + MockGetAuthorityPolicy func(ctx context.Context) (*linkedca.Policy, error) + MockUpdateAuthorityPolicy func(ctx context.Context, policy *linkedca.Policy) error + MockDeleteAuthorityPolicy func(ctx context.Context) error + MockError error MockRet1 interface{} } @@ -179,3 +189,30 @@ func (m *MockDB) DeleteAdmin(ctx context.Context, id string) error { } return m.MockError } + +func (m *MockDB) CreateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + if m.MockCreateAuthorityPolicy != nil { + return m.MockCreateAuthorityPolicy(ctx, policy) + } + return m.MockError +} +func (m *MockDB) GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error) { + if m.MockGetAuthorityPolicy != nil { + return m.MockGetAuthorityPolicy(ctx) + } + return m.MockRet1.(*linkedca.Policy), m.MockError +} + +func (m *MockDB) UpdateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + if m.MockUpdateAuthorityPolicy != nil { + return m.MockUpdateAuthorityPolicy(ctx, policy) + } + return m.MockError +} + +func (m *MockDB) DeleteAuthorityPolicy(ctx context.Context) error { + if m.MockDeleteAuthorityPolicy != nil { + return m.MockDeleteAuthorityPolicy(ctx) + } + return m.MockError +} diff --git a/authority/admin/db/nosql/nosql.go b/authority/admin/db/nosql/nosql.go index 22b049f5..32e05d92 100644 --- a/authority/admin/db/nosql/nosql.go +++ b/authority/admin/db/nosql/nosql.go @@ -11,8 +11,9 @@ import ( ) var ( - adminsTable = []byte("admins") - provisionersTable = []byte("provisioners") + adminsTable = []byte("admins") + provisionersTable = []byte("provisioners") + authorityPoliciesTable = []byte("authority_policies") ) // DB is a struct that implements the AdminDB interface. @@ -23,7 +24,7 @@ type DB struct { // 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} + tables := [][]byte{adminsTable, provisionersTable, authorityPoliciesTable} for _, b := range tables { if err := db.CreateTable(b); err != nil { return nil, errors.Wrapf(err, "error creating table %s", diff --git a/authority/admin/db/nosql/policy.go b/authority/admin/db/nosql/policy.go new file mode 100644 index 00000000..94ff2a0e --- /dev/null +++ b/authority/admin/db/nosql/policy.go @@ -0,0 +1,144 @@ +package nosql + +import ( + "context" + "encoding/json" + + "github.com/pkg/errors" + + "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/nosql" + "go.step.sm/linkedca" +) + +type dbAuthorityPolicy struct { + ID string `json:"id"` + AuthorityID string `json:"authorityID"` + Policy *linkedca.Policy `json:"policy"` +} + +func (dbap *dbAuthorityPolicy) convert() *linkedca.Policy { + return dbap.Policy +} + +func (dbap *dbAuthorityPolicy) clone() *dbAuthorityPolicy { + u := *dbap + return &u +} + +func (db *DB) getDBAuthorityPolicyBytes(ctx context.Context, authorityID string) ([]byte, error) { + data, err := db.db.Get(authorityPoliciesTable, []byte(authorityID)) + if nosql.IsErrNotFound(err) { + return nil, admin.NewError(admin.ErrorNotFoundType, "policy %s not found", authorityID) + } else if err != nil { + return nil, errors.Wrapf(err, "error loading admin %s", authorityID) + } + return data, nil +} + +func (db *DB) unmarshalDBAuthorityPolicy(data []byte, authorityID string) (*dbAuthorityPolicy, error) { + var dba = new(dbAuthorityPolicy) + if err := json.Unmarshal(data, dba); err != nil { + return nil, errors.Wrapf(err, "error unmarshaling admin %s into dbAdmin", authorityID) + } + // if !dba.DeletedAt.IsZero() { + // return nil, admin.NewError(admin.ErrorDeletedType, "admin %s is deleted", authorityID) + // } + 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) getDBAuthorityPolicy(ctx context.Context, authorityID string) (*dbAuthorityPolicy, error) { + data, err := db.getDBAuthorityPolicyBytes(ctx, authorityID) + if err != nil { + return nil, err + } + dbap, err := db.unmarshalDBAuthorityPolicy(data, authorityID) + if err != nil { + return nil, err + } + return dbap, nil +} + +func (db *DB) unmarshalAuthorityPolicy(data []byte, authorityID string) (*linkedca.Policy, error) { + dbap, err := db.unmarshalDBAuthorityPolicy(data, authorityID) + if err != nil { + return nil, err + } + return dbap.convert(), nil +} + +func (db *DB) CreateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + + dbap := &dbAuthorityPolicy{ + ID: db.authorityID, + AuthorityID: db.authorityID, + Policy: policy, + } + + old, err := db.getDBAuthorityPolicy(ctx, db.authorityID) + if err != nil { + return err + } + + return db.save(ctx, dbap.ID, dbap, old, "authority_policy", authorityPoliciesTable) +} + +func (db *DB) GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error) { + // policy := &linkedca.Policy{ + // X509: &linkedca.X509Policy{ + // Allow: &linkedca.X509Names{ + // Dns: []string{".localhost"}, + // }, + // Deny: &linkedca.X509Names{ + // Dns: []string{"denied.localhost"}, + // }, + // }, + // Ssh: &linkedca.SSHPolicy{ + // User: &linkedca.SSHUserPolicy{ + // Allow: &linkedca.SSHUserNames{}, + // Deny: &linkedca.SSHUserNames{}, + // }, + // Host: &linkedca.SSHHostPolicy{ + // Allow: &linkedca.SSHHostNames{}, + // Deny: &linkedca.SSHHostNames{}, + // }, + // }, + // } + + dbap, err := db.getDBAuthorityPolicy(ctx, db.authorityID) + if err != nil { + return nil, err + } + + return dbap.convert(), nil +} + +func (db *DB) UpdateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + old, err := db.getDBAuthorityPolicy(ctx, db.authorityID) + if err != nil { + return err + } + + dbap := &dbAuthorityPolicy{ + ID: db.authorityID, + AuthorityID: db.authorityID, + Policy: policy, + } + + return db.save(ctx, dbap.ID, dbap, old, "authority_policy", authorityPoliciesTable) +} + +func (db *DB) DeleteAuthorityPolicy(ctx context.Context) error { + dbap, err := db.getDBAuthorityPolicy(ctx, db.authorityID) + if err != nil { + return err + } + old := dbap.clone() + + dbap.Policy = nil + return db.save(ctx, dbap.ID, dbap, old, "authority_policy", authorityPoliciesTable) +} diff --git a/authority/admin/db/nosql/provisioner.go b/authority/admin/db/nosql/provisioner.go index 71d9c8d6..540e3ae2 100644 --- a/authority/admin/db/nosql/provisioner.go +++ b/authority/admin/db/nosql/provisioner.go @@ -19,6 +19,7 @@ type dbProvisioner struct { Type linkedca.Provisioner_Type `json:"type"` Name string `json:"name"` Claims *linkedca.Claims `json:"claims"` + Policy *linkedca.Policy `json:"policy"` Details []byte `json:"details"` X509Template *linkedca.Template `json:"x509Template"` SSHTemplate *linkedca.Template `json:"sshTemplate"` @@ -43,6 +44,7 @@ func (dbp *dbProvisioner) convert2linkedca() (*linkedca.Provisioner, error) { Type: dbp.Type, Name: dbp.Name, Claims: dbp.Claims, + Policy: dbp.Policy, Details: details, X509Template: dbp.X509Template, SshTemplate: dbp.SSHTemplate, @@ -160,6 +162,7 @@ func (db *DB) CreateProvisioner(ctx context.Context, prov *linkedca.Provisioner) Type: prov.Type, Name: prov.Name, Claims: prov.Claims, + Policy: prov.Policy, Details: details, X509Template: prov.X509Template, SSHTemplate: prov.SshTemplate, @@ -187,6 +190,7 @@ func (db *DB) UpdateProvisioner(ctx context.Context, prov *linkedca.Provisioner) } nu.Name = prov.Name nu.Claims = prov.Claims + nu.Policy = prov.Policy nu.Details, err = json.Marshal(prov.Details.GetData()) if err != nil { return admin.WrapErrorISE(err, "error marshaling details when updating provisioner %s", prov.Name) diff --git a/authority/authority.go b/authority/authority.go index 4eacfad7..aaf0e478 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -205,6 +205,47 @@ func (a *Authority) reloadAdminResources(ctx context.Context) error { a.provisioners = provClxn a.config.AuthorityConfig.Admins = adminList a.admins = adminClxn + + return nil +} + +// reloadPolicyEngines reloads x509 and SSH policy engines using +// configuration stored in the DB or from the configuration file. +func (a *Authority) reloadPolicyEngines(ctx context.Context) error { + var ( + err error + policyOptions *policy.Options + ) + if a.config.AuthorityConfig.EnableAdmin { + linkedPolicy, err := a.adminDB.GetAuthorityPolicy(ctx) + if err != nil { + return admin.WrapErrorISE(err, "error getting policy to initialize authority") + } + policyOptions = policyToCertificates(linkedPolicy) + } else { + policyOptions = a.config.AuthorityConfig.Policy + } + + // return early if no policy options set + if policyOptions == nil { + return nil + } + + // Initialize the x509 allow/deny policy engine + if a.x509Policy, err = policy.NewX509PolicyEngine(policyOptions.GetX509Options()); err != nil { + return err + } + + // // Initialize the SSH allow/deny policy engine for host certificates + if a.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(policyOptions.GetSSHOptions()); err != nil { + return err + } + + // // Initialize the SSH allow/deny policy engine for user certificates + if a.sshUserPolicy, err = policy.NewSSHUserPolicyEngine(policyOptions.GetSSHOptions()); err != nil { + return err + } + return nil } @@ -533,6 +574,11 @@ func (a *Authority) init() error { return err } + // Load Policy Engines + if err := a.reloadPolicyEngines(context.Background()); err != nil { + return err + } + // Configure templates, currently only ssh templates are supported. if a.sshCAHostCertSignKey != nil || a.sshCAUserCertSignKey != nil { a.templates = a.config.Templates @@ -545,21 +591,6 @@ func (a *Authority) init() error { a.templates.Data["Step"] = tmplVars } - // Initialize the x509 allow/deny policy engine - if a.x509Policy, err = policy.NewX509PolicyEngine(a.config.AuthorityConfig.Policy.GetX509Options()); err != nil { - return err - } - - // // Initialize the SSH allow/deny policy engine for host certificates - if a.sshHostPolicy, err = policy.NewSSHHostPolicyEngine(a.config.AuthorityConfig.Policy.GetSSHOptions()); err != nil { - return err - } - - // // Initialize the SSH allow/deny policy engine for user certificates - if a.sshUserPolicy, err = policy.NewSSHUserPolicyEngine(a.config.AuthorityConfig.Policy.GetSSHOptions()); err != nil { - return err - } - // JWT numeric dates are seconds. a.startTime = time.Now().Truncate(time.Second) // Set flag indicating that initialization has been completed, and should diff --git a/authority/linkedca.go b/authority/linkedca.go index b568dcbb..11c8668c 100644 --- a/authority/linkedca.go +++ b/authority/linkedca.go @@ -15,6 +15,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/db" "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" @@ -34,6 +35,9 @@ type linkedCaClient struct { authorityID string } +// interface guard +var _ admin.DB = (*linkedCaClient)(nil) + type linkedCAClaims struct { jose.Claims SANs []string `json:"sans"` @@ -310,6 +314,22 @@ func (c *linkedCaClient) IsSSHRevoked(serial string) (bool, error) { return resp.Status != linkedca.RevocationStatus_ACTIVE, nil } +func (c *linkedCaClient) CreateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + return errors.New("not implemented yet") +} + +func (c *linkedCaClient) GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error) { + return nil, errors.New("not implemented yet") +} + +func (c *linkedCaClient) UpdateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + return errors.New("not implemented yet") +} + +func (c *linkedCaClient) DeleteAuthorityPolicy(ctx context.Context) error { + return errors.New("not implemented yet") +} + func serializeCertificate(crt *x509.Certificate) string { if crt == nil { return "" diff --git a/authority/policy.go b/authority/policy.go new file mode 100644 index 00000000..8ef264d0 --- /dev/null +++ b/authority/policy.go @@ -0,0 +1,132 @@ +package authority + +import ( + "context" + + "github.com/smallstep/certificates/authority/admin" + "github.com/smallstep/certificates/authority/policy" + "go.step.sm/linkedca" +) + +func (a *Authority) GetAuthorityPolicy(ctx context.Context) (*linkedca.Policy, error) { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + + policy, err := a.adminDB.GetAuthorityPolicy(ctx) + if err != nil { + return nil, err + } + + return policy, nil +} + +func (a *Authority) StoreAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + + if err := a.adminDB.CreateAuthorityPolicy(ctx, policy); err != nil { + return err + } + + if err := a.reloadPolicyEngines(ctx); err != nil { + return admin.WrapErrorISE(err, "error reloading admin resources when creating authority policy") + } + + return nil +} + +func (a *Authority) UpdateAuthorityPolicy(ctx context.Context, policy *linkedca.Policy) error { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + + if err := a.adminDB.UpdateAuthorityPolicy(ctx, policy); err != nil { + return err + } + + if err := a.reloadPolicyEngines(ctx); err != nil { + return admin.WrapErrorISE(err, "error reloading admin resources when updating authority policy") + } + + return nil +} + +func (a *Authority) RemoveAuthorityPolicy(ctx context.Context) error { + a.adminMutex.Lock() + defer a.adminMutex.Unlock() + + if err := a.adminDB.DeleteAuthorityPolicy(ctx); err != nil { + return err + } + + if err := a.reloadPolicyEngines(ctx); err != nil { + return admin.WrapErrorISE(err, "error reloading admin resources when deleting authority policy") + } + + return nil +} + +func policyToCertificates(p *linkedca.Policy) *policy.Options { + // return early + if p == nil { + return nil + } + // prepare full policy struct + opts := &policy.Options{ + X509: &policy.X509PolicyOptions{ + AllowedNames: &policy.X509NameOptions{}, + DeniedNames: &policy.X509NameOptions{}, + }, + SSH: &policy.SSHPolicyOptions{ + Host: &policy.SSHHostCertificateOptions{ + AllowedNames: &policy.SSHNameOptions{}, + DeniedNames: &policy.SSHNameOptions{}, + }, + User: &policy.SSHUserCertificateOptions{ + AllowedNames: &policy.SSHNameOptions{}, + DeniedNames: &policy.SSHNameOptions{}, + }, + }, + } + // fill x509 policy configuration + if p.X509 != nil { + if p.X509.Allow != nil { + opts.X509.AllowedNames.DNSDomains = p.X509.Allow.Dns + opts.X509.AllowedNames.IPRanges = p.X509.Allow.Ips + opts.X509.AllowedNames.EmailAddresses = p.X509.Allow.Emails + opts.X509.AllowedNames.URIDomains = p.X509.Allow.Uris + } + if p.X509.Deny != nil { + opts.X509.DeniedNames.DNSDomains = p.X509.Deny.Dns + opts.X509.DeniedNames.IPRanges = p.X509.Deny.Ips + opts.X509.DeniedNames.EmailAddresses = p.X509.Deny.Emails + opts.X509.DeniedNames.URIDomains = p.X509.Deny.Uris + } + } + // fill ssh policy configuration + if p.Ssh != nil { + if p.Ssh.Host != nil { + if p.Ssh.Host.Allow != nil { + opts.SSH.Host.AllowedNames.DNSDomains = p.Ssh.Host.Allow.Dns + opts.SSH.Host.AllowedNames.IPRanges = p.Ssh.Host.Allow.Ips + opts.SSH.Host.AllowedNames.EmailAddresses = p.Ssh.Host.Allow.Principals + } + if p.Ssh.Host.Deny != nil { + opts.SSH.Host.DeniedNames.DNSDomains = p.Ssh.Host.Deny.Dns + opts.SSH.Host.DeniedNames.IPRanges = p.Ssh.Host.Deny.Ips + opts.SSH.Host.DeniedNames.Principals = p.Ssh.Host.Deny.Principals + } + } + if p.Ssh.User != nil { + if p.Ssh.User.Allow != nil { + opts.SSH.User.AllowedNames.EmailAddresses = p.Ssh.User.Allow.Emails + opts.SSH.User.AllowedNames.Principals = p.Ssh.User.Allow.Principals + } + if p.Ssh.User.Deny != nil { + opts.SSH.User.DeniedNames.EmailAddresses = p.Ssh.User.Deny.Emails + opts.SSH.User.DeniedNames.Principals = p.Ssh.User.Deny.Principals + } + } + } + + return opts +} diff --git a/authority/policy/options.go b/authority/policy/options.go index f57f3bcf..5c6e6134 100644 --- a/authority/policy/options.go +++ b/authority/policy/options.go @@ -1,10 +1,14 @@ package policy +// Options is a container for authority level x509 and SSH +// policy configuration. type Options struct { X509 *X509PolicyOptions `json:"x509,omitempty"` SSH *SSHPolicyOptions `json:"ssh,omitempty"` } +// GetX509Options returns the x509 authority level policy +// configuration func (o *Options) GetX509Options() *X509PolicyOptions { if o == nil { return nil @@ -12,6 +16,8 @@ func (o *Options) GetX509Options() *X509PolicyOptions { return o.X509 } +// GetSSHOptions returns the SSH authority level policy +// configuration func (o *Options) GetSSHOptions() *SSHPolicyOptions { if o == nil { return nil @@ -19,16 +25,19 @@ func (o *Options) GetSSHOptions() *SSHPolicyOptions { return o.SSH } +// X509PolicyOptionsInterface is an interface for providers +// of x509 allowed and denied names. type X509PolicyOptionsInterface interface { GetAllowedNameOptions() *X509NameOptions GetDeniedNameOptions() *X509NameOptions } +// X509PolicyOptions is a container for x509 allowed and denied +// names. type X509PolicyOptions struct { - // AllowedNames ... + // AllowedNames contains the x509 allowed names AllowedNames *X509NameOptions `json:"allow,omitempty"` - - // DeniedNames ... + // DeniedNames contains the x509 denied names DeniedNames *X509NameOptions `json:"deny,omitempty"` } @@ -49,6 +58,8 @@ func (o *X509NameOptions) HasNames() bool { len(o.URIDomains) > 0 } +// SSHPolicyOptionsInterface is an interface for providers of +// SSH user and host name policy configuration. type SSHPolicyOptionsInterface interface { GetAllowedUserNameOptions() *SSHNameOptions GetDeniedUserNameOptions() *SSHNameOptions @@ -56,16 +67,16 @@ type SSHPolicyOptionsInterface interface { GetDeniedHostNameOptions() *SSHNameOptions } +// SSHPolicyOptions is a container for SSH user and host policy +// configuration type SSHPolicyOptions struct { // User contains SSH user certificate options. User *SSHUserCertificateOptions `json:"user,omitempty"` - // Host contains SSH host certificate options. Host *SSHHostCertificateOptions `json:"host,omitempty"` } -// GetAllowedNameOptions returns AllowedNames, which models the -// SANs that ... +// GetAllowedNameOptions returns x509 allowed name policy configuration func (o *X509PolicyOptions) GetAllowedNameOptions() *X509NameOptions { if o == nil { return nil @@ -73,8 +84,7 @@ func (o *X509PolicyOptions) GetAllowedNameOptions() *X509NameOptions { return o.AllowedNames } -// GetDeniedNameOptions returns the DeniedNames, which models the -// SANs that ... +// GetDeniedNameOptions returns the x509 denied name policy configuration func (o *X509PolicyOptions) GetDeniedNameOptions() *X509NameOptions { if o == nil { return nil @@ -82,6 +92,8 @@ func (o *X509PolicyOptions) GetDeniedNameOptions() *X509NameOptions { return o.DeniedNames } +// GetAllowedUserNameOptions returns the SSH allowed user name policy +// configuration. func (o *SSHPolicyOptions) GetAllowedUserNameOptions() *SSHNameOptions { if o == nil { return nil @@ -92,6 +104,8 @@ func (o *SSHPolicyOptions) GetAllowedUserNameOptions() *SSHNameOptions { return o.User.AllowedNames } +// GetDeniedUserNameOptions returns the SSH denied user name policy +// configuration. func (o *SSHPolicyOptions) GetDeniedUserNameOptions() *SSHNameOptions { if o == nil { return nil @@ -102,6 +116,8 @@ func (o *SSHPolicyOptions) GetDeniedUserNameOptions() *SSHNameOptions { return o.User.DeniedNames } +// GetAllowedHostNameOptions returns the SSH allowed host name policy +// configuration. func (o *SSHPolicyOptions) GetAllowedHostNameOptions() *SSHNameOptions { if o == nil { return nil @@ -112,6 +128,8 @@ func (o *SSHPolicyOptions) GetAllowedHostNameOptions() *SSHNameOptions { return o.Host.AllowedNames } +// GetDeniedHostNameOptions returns the SSH denied host name policy +// configuration. func (o *SSHPolicyOptions) GetDeniedHostNameOptions() *SSHNameOptions { if o == nil { return nil diff --git a/authority/provisioners.go b/authority/provisioners.go index 8dc27c6a..7a579267 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority/admin" "github.com/smallstep/certificates/authority/config" + "github.com/smallstep/certificates/authority/policy" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/errs" "go.step.sm/cli-utils/step" @@ -395,6 +396,58 @@ func optionsToCertificates(p *linkedca.Provisioner) *provisioner.Options { ops.SSH.Template = string(p.SshTemplate.Template) ops.SSH.TemplateData = p.SshTemplate.Data } + if p.Policy != nil { + if p.Policy.X509 != nil { + if p.Policy.X509.Allow != nil { + ops.X509.AllowedNames = &policy.X509NameOptions{ + DNSDomains: p.Policy.X509.Allow.Dns, + IPRanges: p.Policy.X509.Allow.Ips, + EmailAddresses: p.Policy.X509.Allow.Emails, + URIDomains: p.Policy.X509.Allow.Uris, + } + } + if p.Policy.X509.Deny != nil { + ops.X509.DeniedNames = &policy.X509NameOptions{ + DNSDomains: p.Policy.X509.Deny.Dns, + IPRanges: p.Policy.X509.Deny.Ips, + EmailAddresses: p.Policy.X509.Deny.Emails, + URIDomains: p.Policy.X509.Deny.Uris, + } + } + } + if p.Policy.Ssh != nil { + if p.Policy.Ssh.Host != nil { + ops.SSH.Host = &policy.SSHHostCertificateOptions{} + if p.Policy.Ssh.Host.Allow != nil { + ops.SSH.Host.AllowedNames = &policy.SSHNameOptions{ + DNSDomains: p.Policy.Ssh.Host.Allow.Dns, + IPRanges: p.Policy.Ssh.Host.Allow.Ips, + } + } + if p.Policy.Ssh.Host.Deny != nil { + ops.SSH.Host.DeniedNames = &policy.SSHNameOptions{ + DNSDomains: p.Policy.Ssh.Host.Deny.Dns, + IPRanges: p.Policy.Ssh.Host.Deny.Ips, + } + } + } + if p.Policy.Ssh.User != nil { + ops.SSH.User = &policy.SSHUserCertificateOptions{} + if p.Policy.Ssh.User.Allow != nil { + ops.SSH.User.AllowedNames = &policy.SSHNameOptions{ + EmailAddresses: p.Policy.Ssh.User.Allow.Emails, + Principals: p.Policy.Ssh.User.Allow.Principals, + } + } + if p.Policy.Ssh.User.Deny != nil { + ops.SSH.User.DeniedNames = &policy.SSHNameOptions{ + EmailAddresses: p.Policy.Ssh.User.Deny.Emails, + Principals: p.Policy.Ssh.User.Deny.Principals, + } + } + } + } + } return ops } diff --git a/authority/tls.go b/authority/tls.go index d749e2ad..96c80e9a 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -192,7 +192,10 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign } // If a policy is configured, perform allow/deny policy check on authority level - if a.x509Policy != nil { + // TODO: policy currently also applies to admin token certs; how to circumvent? + // Allow any name of an admin in the DB? Or in the admin collection? + todoRemoveThis := false + if todoRemoveThis && a.x509Policy != nil { allowed, err := a.x509Policy.AreCertificateNamesAllowed(leaf) if err != nil { return nil, errs.InternalServerErr(err, diff --git a/ca/ca.go b/ca/ca.go index c95ba22f..f4585aba 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -208,7 +208,8 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) { adminDB := auth.GetAdminDatabase() if adminDB != nil { acmeAdminResponder := adminAPI.NewACMEAdminResponder() - adminHandler := adminAPI.NewHandler(auth, adminDB, acmeDB, acmeAdminResponder) + policyAdminResponder := adminAPI.NewPolicyAdminResponder(auth, adminDB) + adminHandler := adminAPI.NewHandler(auth, adminDB, acmeDB, acmeAdminResponder, policyAdminResponder) mux.Route("/admin", func(r chi.Router) { adminHandler.Route(r) }) diff --git a/go.mod b/go.mod index 46fe260c..76cdff9a 100644 --- a/go.mod +++ b/go.mod @@ -49,4 +49,4 @@ require ( // replace github.com/smallstep/nosql => ../nosql // replace go.step.sm/crypto => ../crypto // replace go.step.sm/cli-utils => ../cli-utils -// replace go.step.sm/linkedca => ../linkedca +replace go.step.sm/linkedca => ../linkedca diff --git a/go.sum b/go.sum index 1cd8e2e7..ba7cb531 100644 --- a/go.sum +++ b/go.sum @@ -685,10 +685,6 @@ go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/ go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0= go.step.sm/crypto v0.15.0 h1:VioBln+x3+RoejgeBhvxkLGVYdWRy6PFiAaUUN29/E0= go.step.sm/crypto v0.15.0/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g= -go.step.sm/linkedca v0.9.2 h1:CpAkd174sLXFfrOZrbPEiTzik91QRj3+L0omsiwsiok= -go.step.sm/linkedca v0.9.2/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo= -go.step.sm/linkedca v0.10.0 h1:+bqymMRulHYkVde4l16FnqFVskoS6HCWJN5Z5cxAqF8= -go.step.sm/linkedca v0.10.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo= 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.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=