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{
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,

View file

@ -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{

View file

@ -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{
// 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,17 +111,9 @@ 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
}
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)