diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c90d949a..807cfdd6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: uses: golangci/golangci-lint-action@v2 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: 'v1.45.0' + version: 'v1.45.2' # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b24426a0..046589af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: uses: golangci/golangci-lint-action@v2 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: 'v1.45.0' + version: 'v1.45.2' # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/Makefile b/Makefile index 09e342df..906569f1 100644 --- a/Makefile +++ b/Makefile @@ -151,7 +151,7 @@ integration: bin/$(BINNAME) ######################################### fmt: - $Q gofmt -l -w $(SRC) + $Q gofmt -l -s -w $(SRC) lint: $Q golangci-lint run --timeout=30m diff --git a/authority/admins.go b/authority/admins.go index b975297a..c8e1ac66 100644 --- a/authority/admins.go +++ b/authority/admins.go @@ -49,7 +49,7 @@ func (a *Authority) StoreAdmin(ctx context.Context, adm *linkedca.Admin, prov pr return admin.WrapErrorISE(err, "error creating admin") } if err := a.admins.Store(adm, prov); err != nil { - if err := a.reloadAdminResources(ctx); err != nil { + if err := a.ReloadAdminResources(ctx); err != nil { return admin.WrapErrorISE(err, "error reloading admin resources on failed admin store") } return admin.WrapErrorISE(err, "error storing admin in authority cache") @@ -66,7 +66,7 @@ func (a *Authority) UpdateAdmin(ctx context.Context, id string, nu *linkedca.Adm return nil, admin.WrapErrorISE(err, "error updating cached admin %s", id) } if err := a.adminDB.UpdateAdmin(ctx, adm); err != nil { - if err := a.reloadAdminResources(ctx); err != nil { + if err := a.ReloadAdminResources(ctx); err != nil { return nil, admin.WrapErrorISE(err, "error reloading admin resources on failed admin update") } return nil, admin.WrapErrorISE(err, "error updating admin %s", id) @@ -88,7 +88,7 @@ func (a *Authority) removeAdmin(ctx context.Context, id string) error { return admin.WrapErrorISE(err, "error removing admin %s from authority cache", id) } if err := a.adminDB.DeleteAdmin(ctx, id); err != nil { - if err := a.reloadAdminResources(ctx); err != nil { + if err := a.ReloadAdminResources(ctx); err != nil { return admin.WrapErrorISE(err, "error reloading admin resources on failed admin remove") } return admin.WrapErrorISE(err, "error deleting admin %s", id) diff --git a/authority/authority.go b/authority/authority.go index 8a0013c0..3ce5acfd 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -84,8 +84,12 @@ type Authority struct { policyEngine *policy.Engine adminMutex sync.RWMutex + + // Do Not initialize the authority + skipInit bool } +// Info contains information about the authority. type Info struct { StartTime time.Time RootX509Certs []*x509.Certificate @@ -113,9 +117,11 @@ func New(cfg *config.Config, opts ...Option) (*Authority, error) { } } - // Initialize authority from options or configuration. - if err := a.init(); err != nil { - return nil, err + if !a.skipInit { + // Initialize authority from options or configuration. + if err := a.init(); err != nil { + return nil, err + } } return a, nil @@ -151,16 +157,18 @@ func NewEmbedded(opts ...Option) (*Authority, error) { // Initialize config required fields. a.config.Init() - // Initialize authority from options or configuration. - if err := a.init(); err != nil { - return nil, err + if !a.skipInit { + // Initialize authority from options or configuration. + if err := a.init(); err != nil { + return nil, err + } } return a, nil } -// reloadAdminResources reloads admins and provisioners from the DB. -func (a *Authority) reloadAdminResources(ctx context.Context) error { +// ReloadAdminResources reloads admins and provisioners from the DB. +func (a *Authority) ReloadAdminResources(ctx context.Context) error { var ( provList provisioner.List adminList []*linkedca.Admin @@ -558,7 +566,7 @@ func (a *Authority) init() error { } // Load Provisioners and Admins - if err := a.reloadAdminResources(context.Background()); err != nil { + if err := a.ReloadAdminResources(context.Background()); err != nil { return err } @@ -599,6 +607,12 @@ func (a *Authority) GetAdminDatabase() admin.DB { return a.adminDB } +// GetConfig returns the config. +func (a *Authority) GetConfig() *config.Config { + return a.config +} + +// GetInfo returns information about the authority. func (a *Authority) GetInfo() Info { ai := Info{ StartTime: a.startTime, diff --git a/authority/options.go b/authority/options.go index 1c154577..6e1949f5 100644 --- a/authority/options.go +++ b/authority/options.go @@ -266,6 +266,16 @@ func WithAdminDB(d admin.DB) Option { } } +// WithProvisioners is an option to set the provisioner collection. +// +// Deprecated: provisioner collections will likely change +func WithProvisioners(ps *provisioner.Collection) Option { + return func(a *Authority) error { + a.provisioners = ps + return nil + } +} + // WithLinkedCAToken is an option to set the authentication token used to enable // linked ca. func WithLinkedCAToken(token string) Option { @@ -284,6 +294,15 @@ func WithX509Enforcers(ces ...provisioner.CertificateEnforcer) Option { } } +// WithSkipInit is an option that allows the constructor to skip initializtion +// of the authority. +func WithSkipInit() Option { + return func(a *Authority) error { + a.skipInit = true + return nil + } +} + func readCertificateBundle(pemCerts []byte) ([]*x509.Certificate, error) { var block *pem.Block var certs []*x509.Certificate diff --git a/authority/provisioners.go b/authority/provisioners.go index 76c1a129..1fd34ef0 100644 --- a/authority/provisioners.go +++ b/authority/provisioners.go @@ -148,7 +148,7 @@ func (a *Authority) generateProvisionerConfig(ctx context.Context) (provisioner. } -// StoreProvisioner stores an provisioner.Interface to the authority. +// StoreProvisioner stores a provisioner to the authority. func (a *Authority) StoreProvisioner(ctx context.Context, prov *linkedca.Provisioner) error { a.adminMutex.Lock() defer a.adminMutex.Unlock() @@ -198,7 +198,7 @@ func (a *Authority) StoreProvisioner(ctx context.Context, prov *linkedca.Provisi } if err := a.provisioners.Store(certProv); err != nil { - if err := a.reloadAdminResources(ctx); err != nil { + if err := a.ReloadAdminResources(ctx); err != nil { return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner store") } return admin.WrapErrorISE(err, "error storing provisioner in authority cache") @@ -234,7 +234,7 @@ func (a *Authority) UpdateProvisioner(ctx context.Context, nu *linkedca.Provisio return admin.WrapErrorISE(err, "error updating provisioner '%s' in authority cache", nu.Name) } if err := a.adminDB.UpdateProvisioner(ctx, nu); err != nil { - if err := a.reloadAdminResources(ctx); err != nil { + if err := a.ReloadAdminResources(ctx); err != nil { return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner update") } return admin.WrapErrorISE(err, "error updating provisioner '%s'", nu.Name) @@ -254,31 +254,33 @@ func (a *Authority) RemoveProvisioner(ctx context.Context, id string) error { } provName, provID := p.GetName(), p.GetID() - // Validate - // - Check that there will be SUPER_ADMINs that remain after we - // remove this provisioner. - if a.admins.SuperCount() == a.admins.SuperCountByProvisioner(provName) { - return admin.NewError(admin.ErrorBadRequestType, - "cannot remove provisioner %s because no super admins will remain", provName) - } + if a.IsAdminAPIEnabled() { + // Validate + // - Check that there will be SUPER_ADMINs that remain after we + // remove this provisioner. + if a.IsAdminAPIEnabled() && a.admins.SuperCount() == a.admins.SuperCountByProvisioner(provName) { + return admin.NewError(admin.ErrorBadRequestType, + "cannot remove provisioner %s because no super admins will remain", provName) + } - // Delete all admins associated with the provisioner. - admins, ok := a.admins.LoadByProvisioner(provName) - if ok { - for _, adm := range admins { - if err := a.removeAdmin(ctx, adm.Id); err != nil { - return admin.WrapErrorISE(err, "error deleting admin %s, as part of provisioner %s deletion", adm.Subject, provName) + // Delete all admins associated with the provisioner. + admins, ok := a.admins.LoadByProvisioner(provName) + if ok { + for _, adm := range admins { + if err := a.removeAdmin(ctx, adm.Id); err != nil { + return admin.WrapErrorISE(err, "error deleting admin %s, as part of provisioner %s deletion", adm.Subject, provName) + } } } } // Remove provisioner from authority caches. if err := a.provisioners.Remove(provID); err != nil { - return admin.WrapErrorISE(err, "error removing admin from authority cache") + return admin.WrapErrorISE(err, "error removing provisioner from authority cache") } // Remove provisioner from database. if err := a.adminDB.DeleteProvisioner(ctx, provID); err != nil { - if err := a.reloadAdminResources(ctx); err != nil { + if err := a.ReloadAdminResources(ctx); err != nil { return admin.WrapErrorISE(err, "error reloading admin resources on failed provisioner remove") } return admin.WrapErrorISE(err, "error deleting provisioner %s", provName) diff --git a/ca/adminClient.go b/ca/adminClient.go index bf853e9d..6532b000 100644 --- a/ca/adminClient.go +++ b/ca/adminClient.go @@ -366,19 +366,19 @@ retry: // GetProvisioner performs the GET /admin/provisioners/{name} request to the CA. func (c *AdminClient) GetProvisioner(opts ...ProvisionerOption) (*linkedca.Provisioner, error) { var retried bool - o := new(provisionerOptions) - if err := o.apply(opts); err != nil { + o := new(ProvisionerOptions) + if err := o.Apply(opts); err != nil { return nil, err } var u *url.URL switch { - case len(o.id) > 0: + case o.ID != "": u = c.endpoint.ResolveReference(&url.URL{ Path: "/admin/provisioners/id", RawQuery: o.rawQuery(), }) - case len(o.name) > 0: - u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.name)}) + case o.Name != "": + u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.Name)}) default: return nil, errors.New("must set either name or id in method options") } @@ -413,8 +413,8 @@ retry: // GetProvisionersPaginate performs the GET /admin/provisioners request to the CA. func (c *AdminClient) GetProvisionersPaginate(opts ...ProvisionerOption) (*adminAPI.GetProvisionersResponse, error) { var retried bool - o := new(provisionerOptions) - if err := o.apply(opts); err != nil { + o := new(ProvisionerOptions) + if err := o.Apply(opts); err != nil { return nil, err } u := c.endpoint.ResolveReference(&url.URL{ @@ -475,19 +475,19 @@ func (c *AdminClient) RemoveProvisioner(opts ...ProvisionerOption) error { retried bool ) - o := new(provisionerOptions) - if err := o.apply(opts); err != nil { + o := new(ProvisionerOptions) + if err := o.Apply(opts); err != nil { return err } switch { - case len(o.id) > 0: + case o.ID != "": u = c.endpoint.ResolveReference(&url.URL{ Path: path.Join(adminURLPrefix, "provisioners/id"), RawQuery: o.rawQuery(), }) - case len(o.name) > 0: - u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.name)}) + case o.Name != "": + u = c.endpoint.ResolveReference(&url.URL{Path: path.Join(adminURLPrefix, "provisioners", o.Name)}) default: return errors.New("must set either name or id in method options") } diff --git a/ca/client.go b/ca/client.go index 0bd93195..44961357 100644 --- a/ca/client.go +++ b/ca/client.go @@ -425,16 +425,18 @@ func parseEndpoint(endpoint string) (*url.URL, error) { } // ProvisionerOption is the type of options passed to the Provisioner method. -type ProvisionerOption func(o *provisionerOptions) error +type ProvisionerOption func(o *ProvisionerOptions) error -type provisionerOptions struct { - cursor string - limit int - id string - name string +// ProvisionerOptions stores options for the provisioner CRUD API. +type ProvisionerOptions struct { + Cursor string + Limit int + ID string + Name string } -func (o *provisionerOptions) apply(opts []ProvisionerOption) (err error) { +// Apply caches provisioner options on a struct for later use. +func (o *ProvisionerOptions) Apply(opts []ProvisionerOption) (err error) { for _, fn := range opts { if err = fn(o); err != nil { return @@ -443,51 +445,51 @@ func (o *provisionerOptions) apply(opts []ProvisionerOption) (err error) { return } -func (o *provisionerOptions) rawQuery() string { +func (o *ProvisionerOptions) rawQuery() string { v := url.Values{} - if len(o.cursor) > 0 { - v.Set("cursor", o.cursor) + if o.Cursor != "" { + v.Set("cursor", o.Cursor) } - if o.limit > 0 { - v.Set("limit", strconv.Itoa(o.limit)) + if o.Limit > 0 { + v.Set("limit", strconv.Itoa(o.Limit)) } - if len(o.id) > 0 { - v.Set("id", o.id) + if o.ID != "" { + v.Set("id", o.ID) } - if len(o.name) > 0 { - v.Set("name", o.name) + if o.Name != "" { + v.Set("name", o.Name) } return v.Encode() } // WithProvisionerCursor will request the provisioners starting with the given cursor. func WithProvisionerCursor(cursor string) ProvisionerOption { - return func(o *provisionerOptions) error { - o.cursor = cursor + return func(o *ProvisionerOptions) error { + o.Cursor = cursor return nil } } // WithProvisionerLimit will request the given number of provisioners. func WithProvisionerLimit(limit int) ProvisionerOption { - return func(o *provisionerOptions) error { - o.limit = limit + return func(o *ProvisionerOptions) error { + o.Limit = limit return nil } } // WithProvisionerID will request the given provisioner. func WithProvisionerID(id string) ProvisionerOption { - return func(o *provisionerOptions) error { - o.id = id + return func(o *ProvisionerOptions) error { + o.ID = id return nil } } // WithProvisionerName will request the given provisioner. func WithProvisionerName(name string) ProvisionerOption { - return func(o *provisionerOptions) error { - o.name = name + return func(o *ProvisionerOptions) error { + o.Name = name return nil } } @@ -810,8 +812,8 @@ retry: // paginate the provisioners. func (c *Client) Provisioners(opts ...ProvisionerOption) (*api.ProvisionersResponse, error) { var retried bool - o := new(provisionerOptions) - if err := o.apply(opts); err != nil { + o := new(ProvisionerOptions) + if err := o.Apply(opts); err != nil { return nil, err } u := c.endpoint.ResolveReference(&url.URL{ diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 84e968ab..67c5673d 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -654,7 +654,7 @@ preferably not all - meaning it never leaves the server on which it was created. ### Passwords -When you intialize your PKI (`step ca init`) the root and intermediate +When you initialize your PKI (`step ca init`) the root and intermediate private keys will be encrypted with the same password. We recommend that you change the password with which the intermediate is encrypted at your earliest convenience. @@ -681,7 +681,7 @@ to divide the root private key password across a handful of trusted parties. ### Provisioners -When you intialize your PKI (`step ca init`) a default provisioner will be created +When you initialize your PKI (`step ca init`) a default provisioner will be created and it's private key will be encrypted using the same password used to encrypt the root private key. Before deploying the Step CA you should remove this provisioner and add new ones that are encrypted with new, secure, random passwords.