Initialize the required config fields on embedded authorities.

This change is to make easier the use of embedded authorities. It
can be difficult for third parties to know what fields are required.
The new init methods will define the minimum usable configuration.
This commit is contained in:
Mariano Cano 2020-05-06 13:00:42 -07:00
parent 9499aed6d1
commit 4e544344f9
3 changed files with 45 additions and 26 deletions

View file

@ -67,7 +67,6 @@ func New(config *Config, opts ...Option) (*Authority, error) {
var a = &Authority{ var a = &Authority{
config: config, config: config,
certificates: new(sync.Map), certificates: new(sync.Map),
provisioners: provisioner.NewCollection(config.getAudiences()),
} }
// Apply options. // 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 // NewEmbedded initializes an authority that can be embedded in a different
// project without the limitations of the config. // project without the limitations of the config.
func NewEmbedded(opts ...Option) (*Authority, error) { func NewEmbedded(opts ...Option) (*Authority, error) {
config := &Config{
DNSNames: []string{"localhost", "127.0.0.1", "::1"},
AuthorityConfig: defaultAuthConfig,
TLS: &DefaultTLSOptions,
}
a := &Authority{ a := &Authority{
config: config, config: &Config{},
certificates: new(sync.Map), certificates: new(sync.Map),
provisioners: provisioner.NewCollection(config.getAudiences()),
} }
// Apply options. // Apply options.
@ -108,6 +101,8 @@ func NewEmbedded(opts ...Option) (*Authority, error) {
// Validate required options // Validate required options
switch { 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(): case len(a.rootX509Certs) == 0 && a.config.Root.HasEmpties():
return nil, errors.New("cannot create an authority without a root certificate") return nil, errors.New("cannot create an authority without a root certificate")
case a.x509Issuer == nil && a.config.IntermediateCert == "": 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") 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. // Initialize authority from options or configuration.
if err := a.init(); err != nil { if err := a.init(); err != nil {
return nil, err return nil, err
@ -271,9 +269,11 @@ func (a *Authority) init() error {
return err return err
} }
// Initialize provisioners // Initialize provisioners
audiences := a.config.getAudiences()
a.provisioners = provisioner.NewCollection(audiences)
config := provisioner.Config{ config := provisioner.Config{
Claims: claimer.Claims(), Claims: claimer.Claims(),
Audiences: a.config.getAudiences(), Audiences: audiences,
DB: a.db, DB: a.db,
SSHKeys: &provisioner.SSHKeys{ SSHKeys: &provisioner.SSHKeys{
UserKeys: sshKeys.UserKeys, UserKeys: sshKeys.UserKeys,

View file

@ -207,6 +207,7 @@ func TestNewEmbedded(t *testing.T) {
wantErr bool wantErr bool
}{ }{
{"ok", args{[]Option{WithX509RootBundle(caPEM), WithX509Signer(crt, key.(crypto.Signer))}}, false}, {"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 file", args{[]Option{WithConfigFile("../ca/testdata/ca.json")}}, false},
{"ok config", args{[]Option{WithConfig(&Config{ {"ok config", args{[]Option{WithConfig(&Config{
Root: []string{"testdata/certs/root_ca.crt"}, Root: []string{"testdata/certs/root_ca.crt"},
@ -216,6 +217,7 @@ func TestNewEmbedded(t *testing.T) {
AuthorityConfig: &AuthConfig{}, AuthorityConfig: &AuthConfig{},
})}}, false}, })}}, false},
{"fail options", args{[]Option{WithX509RootBundle([]byte("bad data"))}}, true}, {"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 root", args{[]Option{WithX509Signer(crt, key.(crypto.Signer))}}, true},
{"fail missing signer", args{[]Option{WithX509RootBundle(caPEM)}}, true}, {"fail missing signer", args{[]Option{WithX509RootBundle(caPEM)}}, true},
{"fail missing root file", args{[]Option{WithConfig(&Config{ {"fail missing root file", args{[]Option{WithConfig(&Config{

View file

@ -75,13 +75,20 @@ type AuthConfig struct {
Backdate *provisioner.Duration `json:"backdate,omitempty"` Backdate *provisioner.Duration `json:"backdate,omitempty"`
} }
// defaultAuthConfig used when skipping validation. // init initializes the required fields in the AuthConfig if they are not
var defaultAuthConfig = &AuthConfig{ // provided.
Provisioners: provisioner.List{}, func (c *AuthConfig) init() {
Template: &x509util.ASN1DN{}, if c.Provisioners == nil {
Backdate: &provisioner.Duration{ c.Provisioners = provisioner.List{}
}
if c.Template == nil {
c.Template = &x509util.ASN1DN{}
}
if c.Backdate == nil {
c.Backdate = &provisioner.Duration{
Duration: defaultBackdate, Duration: defaultBackdate,
}, }
}
} }
// Validate validates the authority configuration. // Validate validates the authority configuration.
@ -90,6 +97,9 @@ func (c *AuthConfig) Validate(audiences provisioner.Audiences) error {
return errors.New("authority cannot be undefined") return errors.New("authority cannot be undefined")
} }
// Initialize required fields.
c.init()
// Check that only one K8sSA is enabled // Check that only one K8sSA is enabled
var k8sCount int var k8sCount int
for _, p := range c.Provisioners { for _, p := range c.Provisioners {
@ -101,17 +111,9 @@ func (c *AuthConfig) Validate(audiences provisioner.Audiences) error {
return errors.New("cannot have more than one kubernetes service account provisioner") 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 { if c.Backdate.Duration < 0 {
return errors.New("authority.backdate cannot be less than 0") return errors.New("authority.backdate cannot be less than 0")
} }
} else {
c.Backdate = defaultAuthConfig.Backdate
}
return nil return nil
} }
@ -133,6 +135,21 @@ func LoadConfiguration(filename string) (*Config, error) {
return &c, nil 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. // Save saves the configuration to the given filename.
func (c *Config) Save(filename string) error { func (c *Config) Save(filename string) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)