diff --git a/authority/authority.go b/authority/authority.go index 4ff32150..49351500 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "crypto/x509" "encoding/hex" - "fmt" "sync" "time" @@ -25,7 +24,6 @@ type Authority struct { ottMap *sync.Map startTime time.Time provisioners *provisioner.Collection - audiences []string // Do not re-initialize initOnce bool } @@ -37,19 +35,11 @@ func New(config *Config) (*Authority, error) { return nil, err } - // Define audiences: legacy + possible urls without the ports. - // The CA might have proxies in front so we cannot rely on the port. - audiences := []string{legacyAuthority} - for _, name := range config.DNSNames { - audiences = append(audiences, fmt.Sprintf("https://%s/sign", name), fmt.Sprintf("https://%s/1.0/sign", name)) - } - var a = &Authority{ config: config, certificates: new(sync.Map), ottMap: new(sync.Map), - provisioners: provisioner.NewCollection(audiences), - audiences: audiences, + provisioners: provisioner.NewCollection(config.getAudiences()), } if err := a.init(); err != nil { return nil, err diff --git a/authority/authorize.go b/authority/authorize.go index 75073626..3c46203a 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -84,7 +84,7 @@ func (a *Authority) Authorize(ott string) ([]provisioner.SignOption, error) { // This method will also validate the audiences for JWK provisioners. p, ok := a.provisioners.LoadByToken(token, &claims.Claims) if !ok { - return nil, &apiError{errors.Errorf("authorize: provisioner not found"), + return nil, &apiError{errors.New("authorize: provisioner not found or invalid audience"), http.StatusUnauthorized, errContext} } diff --git a/authority/config.go b/authority/config.go index cac6adc9..44e6b6c2 100644 --- a/authority/config.go +++ b/authority/config.go @@ -2,6 +2,7 @@ package authority import ( "encoding/json" + "fmt" "net" "os" "time" @@ -58,9 +59,8 @@ type AuthConfig struct { } // Validate validates the authority configuration. -func (c *AuthConfig) Validate() error { +func (c *AuthConfig) Validate(audiences []string) error { var err error - if c == nil { return errors.New("authority cannot be undefined") } @@ -71,11 +71,18 @@ func (c *AuthConfig) Validate() error { if c.Claims, err = c.Claims.Init(&globalProvisionerClaims); err != nil { return err } + + // Initialize provisioners + config := provisioner.Config{ + Claims: *c.Claims, + Audiences: audiences, + } for _, p := range c.Provisioners { - if err := p.Init(c.Claims); err != nil { + if err := p.Init(config); err != nil { return err } } + if c.Template == nil { c.Template = &x509util.ASN1DN{} } @@ -154,5 +161,16 @@ func (c *Config) Validate() error { c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation } - return c.AuthorityConfig.Validate() + return c.AuthorityConfig.Validate(c.getAudiences()) +} + +// getAudiences returns the legacy and possible urls without the ports that will +// be used as the default provisioner audiences. The CA might have proxies in +// front so we cannot rely on the port. +func (c *Config) getAudiences() []string { + audiences := []string{legacyAuthority} + for _, name := range c.DNSNames { + audiences = append(audiences, fmt.Sprintf("https://%s/sign", name), fmt.Sprintf("https://%s/1.0/sign", name)) + } + return audiences } diff --git a/authority/provisioner/jwt.go b/authority/provisioner/jwt.go index e4c75dbc..95fbccaa 100644 --- a/authority/provisioner/jwt.go +++ b/authority/provisioner/jwt.go @@ -23,6 +23,7 @@ type JWT struct { Key *jose.JSONWebKey `json:"key,omitempty"` EncryptedKey string `json:"encryptedKey,omitempty"` Claims *Claims `json:"claims,omitempty"` + audiences []string } // GetID returns the provisioner unique identifier. The name and credential id @@ -47,7 +48,7 @@ func (p *JWT) GetEncryptedKey() (string, string, bool) { } // Init initializes and validates a the fields of Provisioner type. -func (p *JWT) Init(global *Claims) (err error) { +func (p *JWT) Init(config Config) (err error) { switch { case p.Name == "": return errors.New("provisioner name cannot be empty") @@ -58,10 +59,12 @@ func (p *JWT) Init(global *Claims) (err error) { case p.Key == nil: return errors.New("provisioner key cannot be empty") } - p.Claims, err = p.Claims.Init(global) + p.Claims, err = p.Claims.Init(&config.Claims) + p.audiences = config.Audiences return err } +// Authorize validates the given token. func (p *JWT) Authorize(token string) ([]SignOption, error) { jwt, err := jose.ParseSigned(token) if err != nil { @@ -81,10 +84,10 @@ func (p *JWT) Authorize(token string) ([]SignOption, error) { return nil, errors.Wrapf(err, "invalid token") } - // if !matchesAudience(claims.Audience, a.audiences) { - // return nil, &apiError{errors.New("authorize: token audience invalid"), http.StatusUnauthorized, - // errContext} - // } + // validate audiences with the defaults + if !matchesAudience(claims.Audience, p.audiences) { + return nil, errors.New("invalid token: invalid audience claim (aud)") + } if claims.Subject == "" { return nil, errors.New("token subject cannot be empty") diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 8a692518..b28b7617 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -73,7 +73,7 @@ func (o *OIDC) GetEncryptedKey() (kid string, key string, ok bool) { } // Init validates and initializes the OIDC provider. -func (o *OIDC) Init(global *Claims) (err error) { +func (o *OIDC) Init(config Config) (err error) { switch { case o.Name == "": return errors.New("name cannot be empty") @@ -84,7 +84,7 @@ func (o *OIDC) Init(global *Claims) (err error) { } // Update claims with global ones - if o.Claims, err = o.Claims.Init(global); err != nil { + if o.Claims, err = o.Claims.Init(&config.Claims); err != nil { return err } // Decode openid-configuration endpoint diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index 2953fa43..d60d2acc 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -14,7 +14,7 @@ type Interface interface { GetName() string GetType() Type GetEncryptedKey() (kid string, key string, ok bool) - Init(claims *Claims) error + Init(config Config) error Authorize(token string) ([]SignOption, error) AuthorizeRenewal(cert *x509.Certificate) error AuthorizeRevoke(token string) error @@ -31,11 +31,20 @@ const ( TypeOIDC Type = 2 ) +// Config defines the default parameters used in the initialization of +// provisioners. +type Config struct { + // Claims are the default claims. + Claims Claims + // Audiences are the audiences used in the default provisioner, (JWK). + Audiences []string +} + type provisioner struct { Type string `json:"type"` } -// Provisioner implmements the provisioner.Interface on a base provisioner. It +// Provisioner implements the provisioner.Interface on a base provisioner. It // also implements custom marshalers and unmarshalers so different provisioners // can be represented in a configuration type. type Provisioner struct { @@ -76,8 +85,8 @@ func (p *Provisioner) GetType() Type { } // Init initializes the base provisioner with the given claims. -func (p *Provisioner) Init(claims *Claims) error { - return p.base.Init(claims) +func (p *Provisioner) Init(c Config) error { + return p.base.Init(c) } // Authorize validates the given token on the base provisioner returning a list @@ -107,7 +116,7 @@ func (p *Provisioner) MarshalJSON() ([]byte, error) { func (p *Provisioner) UnmarshalJSON(data []byte) error { var typ provisioner if err := json.Unmarshal(data, &typ); err != nil { - return errors.Errorf("error unmarshalling provisioner") + return errors.Errorf("error unmarshaling provisioner") } switch strings.ToLower(typ.Type) { @@ -119,7 +128,7 @@ func (p *Provisioner) UnmarshalJSON(data []byte) error { return errors.Errorf("provisioner type %s not supported", typ.Type) } if err := json.Unmarshal(data, &p.base); err != nil { - return errors.Errorf("error unmarshalling provisioner") + return errors.Errorf("error unmarshaling provisioner") } return nil }