forked from TrueCloudLab/certificates
Merge pull request #915 from smallstep/max/removing-beta
exposing authority configuration for provisioner cli commands
This commit is contained in:
commit
f8148071fb
10 changed files with 110 additions and 73 deletions
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -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
|
||||
|
|
2
Makefile
2
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
54
ca/client.go
54
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{
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue