Do not allow reload with database configuration changes.

Fixes #smallstep/ca-component#170
This commit is contained in:
Mariano Cano 2019-05-10 15:58:37 -07:00
parent 2871d0b68b
commit 3a1a4c5ea9
2 changed files with 53 additions and 43 deletions

View file

@ -29,8 +29,19 @@ type Authority struct {
initOnce bool initOnce bool
} }
// Option sets options to the Authority.
type Option func(*Authority)
// WithDatabase sets an already initialize authority database to a new
// authority. This options is intended to be use on graceful reloads.
func WithDatabase(db db.AuthDB) Option {
return func(a *Authority) {
a.db = db
}
}
// New creates and initiates a new Authority type. // New creates and initiates a new Authority type.
func New(config *Config) (*Authority, error) { func New(config *Config, opts ...Option) (*Authority, error) {
err := config.Validate() err := config.Validate()
if err != nil { if err != nil {
return nil, err return nil, err
@ -41,6 +52,9 @@ func New(config *Config) (*Authority, error) {
certificates: new(sync.Map), certificates: new(sync.Map),
provisioners: provisioner.NewCollection(config.getAudiences()), provisioners: provisioner.NewCollection(config.getAudiences()),
} }
for _, opt := range opts {
opt(a)
}
if err := a.init(); err != nil { if err := a.init(); err != nil {
return nil, err return nil, err
} }
@ -55,12 +69,13 @@ func (a *Authority) init() error {
} }
var err error var err error
// Initialize step-ca Database if it's not already initialize with WithDB.
// Initialize step-ca Database.
// If a.config.DB is nil then a simple, barebones in memory DB will be used. // If a.config.DB is nil then a simple, barebones in memory DB will be used.
if a.db == nil {
if a.db, err = db.New(a.config.DB); err != nil { if a.db, err = db.New(a.config.DB); err != nil {
return err return err
} }
}
// Load the root certificates and add them to the certificate store // Load the root certificates and add them to the certificate store
a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root)) a.rootX509Certs = make([]*x509.Certificate, len(a.config.Root))
@ -118,6 +133,12 @@ func (a *Authority) init() error {
return nil return nil
} }
// GetDatabase returns the authority database. It will return null if it's not
// enabled.
func (a *Authority) GetDatabase() db.AuthDB {
return a.db
}
// Shutdown safely shuts down any clients, databases, etc. held by the Authority. // Shutdown safely shuts down any clients, databases, etc. held by the Authority.
func (a *Authority) Shutdown() error { func (a *Authority) Shutdown() error {
return a.db.Shutdown() return a.db.Shutdown()

View file

@ -5,11 +5,13 @@ import (
"crypto/x509" "crypto/x509"
"log" "log"
"net/http" "net/http"
"reflect"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/api" "github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/db"
"github.com/smallstep/certificates/logging" "github.com/smallstep/certificates/logging"
"github.com/smallstep/certificates/monitoring" "github.com/smallstep/certificates/monitoring"
"github.com/smallstep/certificates/server" "github.com/smallstep/certificates/server"
@ -18,6 +20,7 @@ import (
type options struct { type options struct {
configFile string configFile string
password []byte password []byte
database db.AuthDB
} }
func (o *options) apply(opts []Option) { func (o *options) apply(opts []Option) {
@ -45,6 +48,13 @@ func WithPassword(password []byte) Option {
} }
} }
// WithDatabase sets the given authority database to the CA options.
func WithDatabase(db db.AuthDB) Option {
return func(o *options) {
o.database = db
}
}
// CA is the type used to build the complete certificate authority. It builds // CA is the type used to build the complete certificate authority. It builds
// the HTTP server, set ups the middlewares and the HTTP handlers. // the HTTP server, set ups the middlewares and the HTTP handlers.
type CA struct { type CA struct {
@ -71,7 +81,12 @@ func (ca *CA) Init(config *authority.Config) (*CA, error) {
ca.config.Password = string(ca.opts.password) ca.config.Password = string(ca.opts.password)
} }
auth, err := authority.New(config) var opts []authority.Option
if ca.opts.database != nil {
opts = append(opts, authority.WithDatabase(ca.opts.database))
}
auth, err := authority.New(config, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -132,60 +147,34 @@ func (ca *CA) Stop() error {
// Reload reloads the configuration of the CA and calls to the server Reload // Reload reloads the configuration of the CA and calls to the server Reload
// method. // method.
func (ca *CA) Reload() error { func (ca *CA) Reload() error {
var hasDB bool
if ca.config.DB != nil {
hasDB = true
}
if ca.opts.configFile == "" {
return errors.New("error reloading ca: configuration file is not set")
}
config, err := authority.LoadConfiguration(ca.opts.configFile) config, err := authority.LoadConfiguration(ca.opts.configFile)
if err != nil { if err != nil {
return errors.Wrap(err, "error reloading ca configuration") return errors.Wrap(err, "error reloading ca configuration")
} }
logShutDown := func(ss ...string) {
for _, s := range ss {
log.Println(s)
}
log.Println("Continuing to serve requests may result in inconsistent state. Shutting Down ...")
}
logContinue := func(reason string) { logContinue := func(reason string) {
log.Println(reason) log.Println(reason)
log.Println("Continuing to run with the original configuration.") log.Println("Continuing to run with the original configuration.")
log.Println("You can force a restart by sending a SIGTERM signal and then restarting the step-ca.") log.Println("You can force a restart by sending a SIGTERM signal and then restarting the step-ca.")
} }
// Shut down the old authority (shut down the database). If New or Reload // Do not allow reload if the database configuration has changed.
// fails then the CA will continue to run but the database will have been if ca.config.DB != nil && !reflect.DeepEqual(ca.config.DB, config.DB) {
// shutdown, which will cause errors. logContinue("Reload failed because the database configuration has changed.")
if err := ca.auth.Shutdown(); err != nil { return errors.New("error reloading ca: database configuration cannot change")
if hasDB {
logShutDown("Attempt to shut down the ca.Authority has failed.")
return ca.Stop()
} }
logContinue("Reload failed because the ca.Authority could not be shut down.")
return err newCA, err := New(config,
} WithPassword(ca.opts.password),
newCA, err := New(config, WithPassword(ca.opts.password), WithConfigFile(ca.opts.configFile)) WithConfigFile(ca.opts.configFile),
WithDatabase(ca.auth.GetDatabase()),
)
if err != nil { if err != nil {
if hasDB { logContinue("Reload failed because the CA with new configuration could not be initialized.")
logShutDown("Attempt to initialize a CA with the new configuration has failed.",
"The database has already been shutdown.")
return ca.Stop()
}
logContinue("Reload failed because the CA with new configuration could " +
"not be initialized.")
return errors.Wrap(err, "error reloading ca") return errors.Wrap(err, "error reloading ca")
} }
if err = ca.srv.Reload(newCA.srv); err != nil { if err = ca.srv.Reload(newCA.srv); err != nil {
if hasDB {
logShutDown("Attempt to replace the old CA server has failed.",
"The database has already been shutdown.")
return ca.Stop()
}
logContinue("Reload failed because server could not be replaced.") logContinue("Reload failed because server could not be replaced.")
return errors.Wrap(err, "error reloading server") return errors.Wrap(err, "error reloading server")
} }