Add iss#sub uri in OIDC certificates.

Admin will use the CR template if none is provided.
This commit is contained in:
Mariano Cano 2020-07-14 18:30:04 -07:00
parent 9bd576af2c
commit 71be83b25e
2 changed files with 40 additions and 21 deletions

View file

@ -74,10 +74,12 @@ type OIDC struct {
// IsAdmin returns true if the given email is in the Admins allowlist, false // IsAdmin returns true if the given email is in the Admins allowlist, false
// otherwise. // otherwise.
func (o *OIDC) IsAdmin(email string) bool { func (o *OIDC) IsAdmin(email string) bool {
email = sanitizeEmail(email) if email != "" {
for _, e := range o.Admins { email = sanitizeEmail(email)
if email == sanitizeEmail(e) { for _, e := range o.Admins {
return true if email == sanitizeEmail(e) {
return true
}
} }
} }
return false return false
@ -205,13 +207,8 @@ func (o *OIDC) ValidatePayload(p openIDPayload) error {
return errs.Unauthorized("validatePayload: failed to validate oidc token payload: invalid azp") return errs.Unauthorized("validatePayload: failed to validate oidc token payload: invalid azp")
} }
// Enforce an email claim
if p.Email == "" {
return errs.Unauthorized("validatePayload: failed to validate oidc token payload: email not found")
}
// Validate domains (case-insensitive) // Validate domains (case-insensitive)
if !o.IsAdmin(p.Email) && len(o.Domains) > 0 { if p.Email != "" && len(o.Domains) > 0 && !o.IsAdmin(p.Email) {
email := sanitizeEmail(p.Email) email := sanitizeEmail(p.Email)
var found bool var found bool
for _, d := range o.Domains { for _, d := range o.Domains {
@ -304,15 +301,38 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e
} }
// Certificate templates // Certificate templates
data := x509util.CreateTemplateData(claims.Subject, []string{claims.Email}) sans := []string{}
if claims.Email != "" {
sans = append(sans, claims.Email)
}
// Add uri SAN with iss#sub if issuer is a URL with schema.
//
// According to https://openid.net/specs/openid-connect-core-1_0.html the
// iss value is a case sensitive URL using the https scheme that contains
// scheme, host, and optionally, port number and path components and no
// query or fragment components.
if iss, err := url.Parse(claims.Issuer); err == nil && iss.Scheme != "" {
iss.Fragment = claims.Subject
sans = append(sans, iss.String())
}
data := x509util.CreateTemplateData(claims.Subject, sans)
data.SetToken(claims) data.SetToken(claims)
templateOptions, err := TemplateOptions(o.Options, data) // Use the default template unless no-templates are configured and email is
// an admin, in that case we will use the CR template.
defaultTemplate := x509util.DefaultLeafTemplate
if !o.Options.HasTemplate() && o.IsAdmin(claims.Email) {
defaultTemplate = x509util.CertificateRequestTemplate
}
templateOptions, err := CustomTemplateOptions(o.Options, data, defaultTemplate)
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSign") return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSign")
} }
so := []SignOption{ return []SignOption{
templateOptions, templateOptions,
// modifiers / withOptions // modifiers / withOptions
newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID), newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID),
@ -320,13 +340,7 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e
// validators // validators
defaultPublicKeyValidator{}, defaultPublicKeyValidator{},
newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()), newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()),
} }, nil
// Admins should be able to authorize any SAN
if o.IsAdmin(claims.Email) {
return so, nil
}
return append(so, emailOnlyIdentity(claims.Email)), nil
} }
// AuthorizeRenew returns an error if the renewal is disabled. // AuthorizeRenew returns an error if the renewal is disabled.

View file

@ -35,6 +35,11 @@ type ProvisionerOptions struct {
TemplateData json.RawMessage `json:"templateData"` TemplateData json.RawMessage `json:"templateData"`
} }
// HasTemplate returns true if a template is defined in the provisioner options.
func (o *ProvisionerOptions) HasTemplate() bool {
return o != nil && (o.Template != "" || o.TemplateFile != "")
}
// TemplateOptions generates a CertificateOptions with the template and data // TemplateOptions generates a CertificateOptions with the template and data
// defined in the ProvisionerOptions, the provisioner generated data, and the // defined in the ProvisionerOptions, the provisioner generated data, and the
// user data provided in the request. If no template has been provided, // user data provided in the request. If no template has been provided,
@ -63,7 +68,7 @@ func CustomTemplateOptions(o *ProvisionerOptions, data x509util.TemplateData, de
return certificateOptionsFunc(func(so Options) []x509util.Option { return certificateOptionsFunc(func(so Options) []x509util.Option {
// We're not provided user data without custom templates. // We're not provided user data without custom templates.
if o == nil || (o.Template == "" && o.TemplateFile == "") { if !o.HasTemplate() {
return []x509util.Option{ return []x509util.Option{
x509util.WithTemplate(defaultTemplate, data), x509util.WithTemplate(defaultTemplate, data),
} }