diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index a5675a2a..77cf69cc 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -74,10 +74,12 @@ type OIDC struct { // IsAdmin returns true if the given email is in the Admins allowlist, false // otherwise. func (o *OIDC) IsAdmin(email string) bool { - email = sanitizeEmail(email) - for _, e := range o.Admins { - if email == sanitizeEmail(e) { - return true + if email != "" { + email = sanitizeEmail(email) + for _, e := range o.Admins { + if email == sanitizeEmail(e) { + return true + } } } 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") } - // Enforce an email claim - if p.Email == "" { - return errs.Unauthorized("validatePayload: failed to validate oidc token payload: email not found") - } - // 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) var found bool for _, d := range o.Domains { @@ -304,15 +301,38 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e } // 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) - 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 { return nil, errs.Wrap(http.StatusInternalServerError, err, "oidc.AuthorizeSign") } - so := []SignOption{ + return []SignOption{ templateOptions, // modifiers / withOptions newProvisionerExtensionOption(TypeOIDC, o.Name, o.ClientID), @@ -320,13 +340,7 @@ func (o *OIDC) AuthorizeSign(ctx context.Context, token string) ([]SignOption, e // validators defaultPublicKeyValidator{}, newValidityValidator(o.claimer.MinTLSCertDuration(), o.claimer.MaxTLSCertDuration()), - } - // Admins should be able to authorize any SAN - if o.IsAdmin(claims.Email) { - return so, nil - } - - return append(so, emailOnlyIdentity(claims.Email)), nil + }, nil } // AuthorizeRenew returns an error if the renewal is disabled. diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index 96165d08..dee04ff5 100644 --- a/authority/provisioner/options.go +++ b/authority/provisioner/options.go @@ -35,6 +35,11 @@ type ProvisionerOptions struct { 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 // defined in the ProvisionerOptions, the provisioner generated data, and the // 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 { // We're not provided user data without custom templates. - if o == nil || (o.Template == "" && o.TemplateFile == "") { + if !o.HasTemplate() { return []x509util.Option{ x509util.WithTemplate(defaultTemplate, data), }