Merge pull request #915 from smallstep/max/removing-beta

exposing authority configuration for provisioner cli commands
This commit is contained in:
Max 2022-05-19 22:53:59 -07:00 committed by GitHub
commit f8148071fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 110 additions and 73 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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,

View file

@ -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

View file

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

View file

@ -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")
}

View file

@ -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{

View file

@ -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.