forked from TrueCloudLab/certificates
Validate audiences in the default provisioner.
This commit is contained in:
parent
33c1449360
commit
2d00cd0933
6 changed files with 50 additions and 30 deletions
|
@ -4,7 +4,6 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -25,7 +24,6 @@ type Authority struct {
|
||||||
ottMap *sync.Map
|
ottMap *sync.Map
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
provisioners *provisioner.Collection
|
provisioners *provisioner.Collection
|
||||||
audiences []string
|
|
||||||
// Do not re-initialize
|
// Do not re-initialize
|
||||||
initOnce bool
|
initOnce bool
|
||||||
}
|
}
|
||||||
|
@ -37,19 +35,11 @@ func New(config *Config) (*Authority, error) {
|
||||||
return nil, err
|
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{
|
var a = &Authority{
|
||||||
config: config,
|
config: config,
|
||||||
certificates: new(sync.Map),
|
certificates: new(sync.Map),
|
||||||
ottMap: new(sync.Map),
|
ottMap: new(sync.Map),
|
||||||
provisioners: provisioner.NewCollection(audiences),
|
provisioners: provisioner.NewCollection(config.getAudiences()),
|
||||||
audiences: audiences,
|
|
||||||
}
|
}
|
||||||
if err := a.init(); err != nil {
|
if err := a.init(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -84,7 +84,7 @@ func (a *Authority) Authorize(ott string) ([]provisioner.SignOption, error) {
|
||||||
// This method will also validate the audiences for JWK provisioners.
|
// This method will also validate the audiences for JWK provisioners.
|
||||||
p, ok := a.provisioners.LoadByToken(token, &claims.Claims)
|
p, ok := a.provisioners.LoadByToken(token, &claims.Claims)
|
||||||
if !ok {
|
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}
|
http.StatusUnauthorized, errContext}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package authority
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
@ -58,9 +59,8 @@ type AuthConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the authority configuration.
|
// Validate validates the authority configuration.
|
||||||
func (c *AuthConfig) Validate() error {
|
func (c *AuthConfig) Validate(audiences []string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return errors.New("authority cannot be undefined")
|
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 {
|
if c.Claims, err = c.Claims.Init(&globalProvisionerClaims); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize provisioners
|
||||||
|
config := provisioner.Config{
|
||||||
|
Claims: *c.Claims,
|
||||||
|
Audiences: audiences,
|
||||||
|
}
|
||||||
for _, p := range c.Provisioners {
|
for _, p := range c.Provisioners {
|
||||||
if err := p.Init(c.Claims); err != nil {
|
if err := p.Init(config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Template == nil {
|
if c.Template == nil {
|
||||||
c.Template = &x509util.ASN1DN{}
|
c.Template = &x509util.ASN1DN{}
|
||||||
}
|
}
|
||||||
|
@ -154,5 +161,16 @@ func (c *Config) Validate() error {
|
||||||
c.TLS.Renegotiation = c.TLS.Renegotiation || DefaultTLSOptions.Renegotiation
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ type JWT struct {
|
||||||
Key *jose.JSONWebKey `json:"key,omitempty"`
|
Key *jose.JSONWebKey `json:"key,omitempty"`
|
||||||
EncryptedKey string `json:"encryptedKey,omitempty"`
|
EncryptedKey string `json:"encryptedKey,omitempty"`
|
||||||
Claims *Claims `json:"claims,omitempty"`
|
Claims *Claims `json:"claims,omitempty"`
|
||||||
|
audiences []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetID returns the provisioner unique identifier. The name and credential id
|
// 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.
|
// 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 {
|
switch {
|
||||||
case p.Name == "":
|
case p.Name == "":
|
||||||
return errors.New("provisioner name cannot be empty")
|
return errors.New("provisioner name cannot be empty")
|
||||||
|
@ -58,10 +59,12 @@ func (p *JWT) Init(global *Claims) (err error) {
|
||||||
case p.Key == nil:
|
case p.Key == nil:
|
||||||
return errors.New("provisioner key cannot be empty")
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Authorize validates the given token.
|
||||||
func (p *JWT) Authorize(token string) ([]SignOption, error) {
|
func (p *JWT) Authorize(token string) ([]SignOption, error) {
|
||||||
jwt, err := jose.ParseSigned(token)
|
jwt, err := jose.ParseSigned(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -81,10 +84,10 @@ func (p *JWT) Authorize(token string) ([]SignOption, error) {
|
||||||
return nil, errors.Wrapf(err, "invalid token")
|
return nil, errors.Wrapf(err, "invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if !matchesAudience(claims.Audience, a.audiences) {
|
// validate audiences with the defaults
|
||||||
// return nil, &apiError{errors.New("authorize: token audience invalid"), http.StatusUnauthorized,
|
if !matchesAudience(claims.Audience, p.audiences) {
|
||||||
// errContext}
|
return nil, errors.New("invalid token: invalid audience claim (aud)")
|
||||||
// }
|
}
|
||||||
|
|
||||||
if claims.Subject == "" {
|
if claims.Subject == "" {
|
||||||
return nil, errors.New("token subject cannot be empty")
|
return nil, errors.New("token subject cannot be empty")
|
||||||
|
|
|
@ -73,7 +73,7 @@ func (o *OIDC) GetEncryptedKey() (kid string, key string, ok bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init validates and initializes the OIDC provider.
|
// Init validates and initializes the OIDC provider.
|
||||||
func (o *OIDC) Init(global *Claims) (err error) {
|
func (o *OIDC) Init(config Config) (err error) {
|
||||||
switch {
|
switch {
|
||||||
case o.Name == "":
|
case o.Name == "":
|
||||||
return errors.New("name cannot be empty")
|
return errors.New("name cannot be empty")
|
||||||
|
@ -84,7 +84,7 @@ func (o *OIDC) Init(global *Claims) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update claims with global ones
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
// Decode openid-configuration endpoint
|
// Decode openid-configuration endpoint
|
||||||
|
|
|
@ -14,7 +14,7 @@ type Interface interface {
|
||||||
GetName() string
|
GetName() string
|
||||||
GetType() Type
|
GetType() Type
|
||||||
GetEncryptedKey() (kid string, key string, ok bool)
|
GetEncryptedKey() (kid string, key string, ok bool)
|
||||||
Init(claims *Claims) error
|
Init(config Config) error
|
||||||
Authorize(token string) ([]SignOption, error)
|
Authorize(token string) ([]SignOption, error)
|
||||||
AuthorizeRenewal(cert *x509.Certificate) error
|
AuthorizeRenewal(cert *x509.Certificate) error
|
||||||
AuthorizeRevoke(token string) error
|
AuthorizeRevoke(token string) error
|
||||||
|
@ -31,11 +31,20 @@ const (
|
||||||
TypeOIDC Type = 2
|
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 provisioner struct {
|
||||||
Type string `json:"type"`
|
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
|
// also implements custom marshalers and unmarshalers so different provisioners
|
||||||
// can be represented in a configuration type.
|
// can be represented in a configuration type.
|
||||||
type Provisioner struct {
|
type Provisioner struct {
|
||||||
|
@ -76,8 +85,8 @@ func (p *Provisioner) GetType() Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the base provisioner with the given claims.
|
// Init initializes the base provisioner with the given claims.
|
||||||
func (p *Provisioner) Init(claims *Claims) error {
|
func (p *Provisioner) Init(c Config) error {
|
||||||
return p.base.Init(claims)
|
return p.base.Init(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorize validates the given token on the base provisioner returning a list
|
// 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 {
|
func (p *Provisioner) UnmarshalJSON(data []byte) error {
|
||||||
var typ provisioner
|
var typ provisioner
|
||||||
if err := json.Unmarshal(data, &typ); err != nil {
|
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) {
|
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)
|
return errors.Errorf("provisioner type %s not supported", typ.Type)
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(data, &p.base); err != nil {
|
if err := json.Unmarshal(data, &p.base); err != nil {
|
||||||
return errors.Errorf("error unmarshalling provisioner")
|
return errors.Errorf("error unmarshaling provisioner")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue