Improve SCEP authority initialization and reload

pull/1523/head
Herman Slatman 10 months ago
parent 7163c4f95f
commit fc1fb51854
No known key found for this signature in database
GPG Key ID: F4D8A44EA0A75A4F

@ -262,13 +262,22 @@ func (a *Authority) ReloadAdminResources(ctx context.Context) error {
a.config.AuthorityConfig.Admins = adminList a.config.AuthorityConfig.Admins = adminList
a.admins = adminClxn a.admins = adminClxn
// update the SCEP Authority with the currently active SCEP switch {
// provisioner names and revalidate the configuration. case a.requiresSCEP() && a.GetSCEP() == nil:
if a.scepAuthority != nil { // TODO(hs): try to initialize SCEP here too? It's a bit
// problematic if this method is called as part of an update
// via Admin API and a password needs to be provided.
case a.requiresSCEP() && a.GetSCEP() != nil:
// update the SCEP Authority with the currently active SCEP
// provisioner names and revalidate the configuration.
a.scepAuthority.UpdateProvisioners(a.getSCEPProvisionerNames()) a.scepAuthority.UpdateProvisioners(a.getSCEPProvisionerNames())
if err := a.scepAuthority.Validate(); err != nil { if err := a.scepAuthority.Validate(); err != nil {
log.Printf("failed validating SCEP authority: %v\n", err) log.Printf("failed validating SCEP authority: %v\n", err)
} }
case !a.requiresSCEP() && a.GetSCEP() != nil:
// TODO(hs): don't remove the authority if we can't also
// reload it.
//a.scepAuthority = nil
} }
return nil return nil
@ -651,14 +660,17 @@ func (a *Authority) init() error {
} }
// The SCEP functionality is provided through an instance of // The SCEP functionality is provided through an instance of
// scep.Authority. It is initialized once when the CA is started. // scep.Authority. It is initialized when the CA is started and
// TODO(hs): should the SCEP Authority support reloading? For example, // if it doesn't exist yet. It gets refreshed if it already
// when the admin resources are reloaded, specifically the provisioners, // exists. If the SCEP authority is no longer required on reload,
// it can happen that the SCEP Authority is no longer required and can // it gets removed.
// be destroyed, or that it needs to be instantiated. It may also need // TODO(hs): reloading through SIGHUP doesn't hit these cases. This
// to be revalidated, because not all SCEP provisioner may have a // is because an entirely new authority.Authority is created, including
// valid decrypter available. // a new scep.Authority. Look into this to see if we want this to
if a.requiresSCEP() && a.GetSCEP() == nil { // keep working like that, or want to reuse a single instance and
// update that.
switch {
case a.requiresSCEP() && a.GetSCEP() == nil:
var options scep.Options var options scep.Options
options.Roots = a.rootX509Certs options.Roots = a.rootX509Certs
options.Intermediates, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert) options.Intermediates, err = pemutil.ReadCertificateBundle(a.config.IntermediateCert)
@ -698,15 +710,28 @@ func (a *Authority) init() error {
options.SCEPProvisionerNames = a.getSCEPProvisionerNames() options.SCEPProvisionerNames = a.getSCEPProvisionerNames()
// create a new SCEP authority // create a new SCEP authority
a.scepAuthority, err = scep.New(a, options) scepAuthority, err := scep.New(a, options)
if err != nil { if err != nil {
return err return err
} }
// validate the SCEP authority // validate the SCEP authority
if err := a.scepAuthority.Validate(); err != nil { if err := scepAuthority.Validate(); err != nil {
a.initLogf("failed validating SCEP authority: %v", err) a.initLogf("failed validating SCEP authority: %v", err)
} }
// set the SCEP authority
a.scepAuthority = scepAuthority
case !a.requiresSCEP() && a.GetSCEP() != nil:
// clear the SCEP authority if it's no longer required
a.scepAuthority = nil
case a.requiresSCEP() && a.GetSCEP() != nil:
// update the SCEP Authority with the currently active SCEP
// provisioner names and revalidate the configuration.
a.scepAuthority.UpdateProvisioners(a.getSCEPProvisionerNames())
if err := a.scepAuthority.Validate(); err != nil {
log.Printf("failed validating SCEP authority: %v\n", err)
}
} }
// Load X509 constraints engine. // Load X509 constraints engine.
@ -869,12 +894,15 @@ func (a *Authority) requiresSCEP() bool {
return false return false
} }
// getSCEPProvisionerNames returns the names of the SCEP provisioners
// that are currently available in the CA.
func (a *Authority) getSCEPProvisionerNames() (names []string) { func (a *Authority) getSCEPProvisionerNames() (names []string) {
for _, p := range a.config.AuthorityConfig.Provisioners { for _, p := range a.config.AuthorityConfig.Provisioners {
if p.GetType() == provisioner.TypeSCEP { if p.GetType() == provisioner.TypeSCEP {
names = append(names, p.GetName()) names = append(names, p.GetName())
} }
} }
return return
} }

@ -250,19 +250,9 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
var scepAuthority *scep.Authority var scepAuthority *scep.Authority
if ca.shouldServeSCEPEndpoints() { if ca.shouldServeSCEPEndpoints() {
// validate the SCEP authority configuration. Currently this // get the SCEP authority configuration. Validation is
// will not result in a failure to start if one or more SCEP // performed within the authority instantiation process.
// provisioners are not correctly configured. Only a log will
// be emitted.
scepAuthority = auth.GetSCEP() scepAuthority = auth.GetSCEP()
if err := scepAuthority.Validate(); err != nil {
err = errors.Wrap(err, "failed validating SCEP authority")
shouldFail := false
if shouldFail {
return nil, err
}
log.Println(err)
}
// According to the RFC (https://tools.ietf.org/html/rfc8894#section-7.10), // According to the RFC (https://tools.ietf.org/html/rfc8894#section-7.10),
// SCEP operations are performed using HTTP, so that's why the API is mounted // SCEP operations are performed using HTTP, so that's why the API is mounted

@ -6,6 +6,7 @@ import (
"crypto/x509" "crypto/x509"
"errors" "errors"
"fmt" "fmt"
"sync"
microx509util "github.com/micromdm/scep/v2/cryptoutil/x509util" microx509util "github.com/micromdm/scep/v2/cryptoutil/x509util"
microscep "github.com/micromdm/scep/v2/scep" microscep "github.com/micromdm/scep/v2/scep"
@ -25,6 +26,8 @@ type Authority struct {
signer crypto.Signer signer crypto.Signer
defaultDecrypter crypto.Decrypter defaultDecrypter crypto.Decrypter
scepProvisionerNames []string scepProvisionerNames []string
mu sync.RWMutex
} }
type authorityKey struct{} type authorityKey struct{}
@ -77,6 +80,13 @@ func New(signAuth SignAuthority, opts Options) (*Authority, error) {
// The validation includes a check if a decrypter is available, either // The validation includes a check if a decrypter is available, either
// an authority wide decrypter, or a provisioner specific decrypter. // an authority wide decrypter, or a provisioner specific decrypter.
func (a *Authority) Validate() error { func (a *Authority) Validate() error {
if a == nil {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
noDefaultDecrypterAvailable := a.defaultDecrypter == nil noDefaultDecrypterAvailable := a.defaultDecrypter == nil
for _, name := range a.scepProvisionerNames { for _, name := range a.scepProvisionerNames {
p, err := a.LoadProvisionerByName(name) p, err := a.LoadProvisionerByName(name)
@ -102,6 +112,13 @@ func (a *Authority) Validate() error {
// current SCEP provisioners configured. This allows the Authority to be // current SCEP provisioners configured. This allows the Authority to be
// validated with the latest data. // validated with the latest data.
func (a *Authority) UpdateProvisioners(scepProvisionerNames []string) { func (a *Authority) UpdateProvisioners(scepProvisionerNames []string) {
if a == nil {
return
}
a.mu.Lock()
defer a.mu.Unlock()
a.scepProvisionerNames = scepProvisionerNames a.scepProvisionerNames = scepProvisionerNames
} }

Loading…
Cancel
Save