diff --git a/authority/authority.go b/authority/authority.go index 411787b6..828adf2f 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -67,7 +67,6 @@ func New(config *Config, opts ...Option) (*Authority, error) { var a = &Authority{ config: config, certificates: new(sync.Map), - provisioners: provisioner.NewCollection(config.getAudiences()), } // Apply options. @@ -88,15 +87,9 @@ func New(config *Config, opts ...Option) (*Authority, error) { // NewEmbedded initializes an authority that can be embedded in a different // project without the limitations of the config. func NewEmbedded(opts ...Option) (*Authority, error) { - config := &Config{ - DNSNames: []string{"localhost", "127.0.0.1", "::1"}, - AuthorityConfig: defaultAuthConfig, - TLS: &DefaultTLSOptions, - } a := &Authority{ - config: config, + config: &Config{}, certificates: new(sync.Map), - provisioners: provisioner.NewCollection(config.getAudiences()), } // Apply options. @@ -108,6 +101,8 @@ func NewEmbedded(opts ...Option) (*Authority, error) { // Validate required options switch { + case a.config == nil: + return nil, errors.New("cannot create an authority without a configuration") case len(a.rootX509Certs) == 0 && a.config.Root.HasEmpties(): return nil, errors.New("cannot create an authority without a root certificate") case a.x509Issuer == nil && a.config.IntermediateCert == "": @@ -116,6 +111,9 @@ func NewEmbedded(opts ...Option) (*Authority, error) { return nil, errors.New("cannot create an authority without an issuer signer") } + // Initialize config required fields. + a.config.init() + // Initialize authority from options or configuration. if err := a.init(); err != nil { return nil, err @@ -271,9 +269,11 @@ func (a *Authority) init() error { return err } // Initialize provisioners + audiences := a.config.getAudiences() + a.provisioners = provisioner.NewCollection(audiences) config := provisioner.Config{ Claims: claimer.Claims(), - Audiences: a.config.getAudiences(), + Audiences: audiences, DB: a.db, SSHKeys: &provisioner.SSHKeys{ UserKeys: sshKeys.UserKeys, diff --git a/authority/authority_test.go b/authority/authority_test.go index b8cab30c..3ab3e142 100644 --- a/authority/authority_test.go +++ b/authority/authority_test.go @@ -207,6 +207,7 @@ func TestNewEmbedded(t *testing.T) { wantErr bool }{ {"ok", args{[]Option{WithX509RootBundle(caPEM), WithX509Signer(crt, key.(crypto.Signer))}}, false}, + {"ok empty config", args{[]Option{WithConfig(&Config{}), WithX509RootBundle(caPEM), WithX509Signer(crt, key.(crypto.Signer))}}, false}, {"ok config file", args{[]Option{WithConfigFile("../ca/testdata/ca.json")}}, false}, {"ok config", args{[]Option{WithConfig(&Config{ Root: []string{"testdata/certs/root_ca.crt"}, @@ -216,6 +217,7 @@ func TestNewEmbedded(t *testing.T) { AuthorityConfig: &AuthConfig{}, })}}, false}, {"fail options", args{[]Option{WithX509RootBundle([]byte("bad data"))}}, true}, + {"fail missing config", args{[]Option{WithConfig(nil), WithX509RootBundle(caPEM), WithX509Signer(crt, key.(crypto.Signer))}}, true}, {"fail missing root", args{[]Option{WithX509Signer(crt, key.(crypto.Signer))}}, true}, {"fail missing signer", args{[]Option{WithX509RootBundle(caPEM)}}, true}, {"fail missing root file", args{[]Option{WithConfig(&Config{ diff --git a/authority/config.go b/authority/config.go index 5d951853..a26d19ad 100644 --- a/authority/config.go +++ b/authority/config.go @@ -75,13 +75,20 @@ type AuthConfig struct { Backdate *provisioner.Duration `json:"backdate,omitempty"` } -// defaultAuthConfig used when skipping validation. -var defaultAuthConfig = &AuthConfig{ - Provisioners: provisioner.List{}, - Template: &x509util.ASN1DN{}, - Backdate: &provisioner.Duration{ - Duration: defaultBackdate, - }, +// init initializes the required fields in the AuthConfig if they are not +// provided. +func (c *AuthConfig) init() { + if c.Provisioners == nil { + c.Provisioners = provisioner.List{} + } + if c.Template == nil { + c.Template = &x509util.ASN1DN{} + } + if c.Backdate == nil { + c.Backdate = &provisioner.Duration{ + Duration: defaultBackdate, + } + } } // Validate validates the authority configuration. @@ -90,6 +97,9 @@ func (c *AuthConfig) Validate(audiences provisioner.Audiences) error { return errors.New("authority cannot be undefined") } + // Initialize required fields. + c.init() + // Check that only one K8sSA is enabled var k8sCount int for _, p := range c.Provisioners { @@ -101,16 +111,8 @@ func (c *AuthConfig) Validate(audiences provisioner.Audiences) error { return errors.New("cannot have more than one kubernetes service account provisioner") } - if c.Template == nil { - c.Template = defaultAuthConfig.Template - } - - if c.Backdate != nil { - if c.Backdate.Duration < 0 { - return errors.New("authority.backdate cannot be less than 0") - } - } else { - c.Backdate = defaultAuthConfig.Backdate + if c.Backdate.Duration < 0 { + return errors.New("authority.backdate cannot be less than 0") } return nil @@ -133,6 +135,21 @@ func LoadConfiguration(filename string) (*Config, error) { return &c, nil } +// initializes the minimal configuration required to create an authority. This +// is mainly used on embedded authorities. +func (c *Config) init() { + if c.DNSNames == nil { + c.DNSNames = []string{"localhost", "127.0.0.1", "::1"} + } + if c.TLS == nil { + c.TLS = &DefaultTLSOptions + } + if c.AuthorityConfig == nil { + c.AuthorityConfig = &AuthConfig{} + } + c.AuthorityConfig.init() +} + // Save saves the configuration to the given filename. func (c *Config) Save(filename string) error { f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)