diff --git a/acme/account.go b/acme/account.go index 51225b49..18c0d646 100644 --- a/acme/account.go +++ b/acme/account.go @@ -81,6 +81,20 @@ func (p *Policy) GetDeniedNameOptions() *policy.X509NameOptions { } } +// IsWildcardLiteralAllowed returns true by default for +// ACME account policies, as authorization is performed on DNS +// level. +func (p *Policy) IsWildcardLiteralAllowed() bool { + return true +} + +// ShouldVerifySubjectCommonName returns true by default +// for ACME account policies, as this is embedded in the +// protocol. +func (p *Policy) ShouldVerifySubjectCommonName() bool { + return true +} + // ExternalAccountKey is an ACME External Account Binding key. type ExternalAccountKey struct { ID string `json:"id"` diff --git a/authority/admin/api/policy.go b/authority/admin/api/policy.go index b7c7855f..d294060c 100644 --- a/authority/admin/api/policy.go +++ b/authority/admin/api/policy.go @@ -2,9 +2,11 @@ package api import ( "errors" + "fmt" "net/http" "go.step.sm/linkedca" + "google.golang.org/protobuf/types/known/wrapperspb" "github.com/smallstep/certificates/acme" "github.com/smallstep/certificates/api/read" @@ -85,6 +87,10 @@ func (par *PolicyAdminResponder) CreateAuthorityPolicy(w http.ResponseWriter, r return } + fmt.Println("before: ", newPolicy) + applyDefaults(newPolicy) + fmt.Println("after: ", newPolicy) + adm := linkedca.AdminFromContext(ctx) var createdPolicy *linkedca.Policy @@ -202,6 +208,8 @@ func (par *PolicyAdminResponder) CreateProvisionerPolicy(w http.ResponseWriter, return } + applyDefaults(newPolicy) + prov.Policy = newPolicy if err := par.auth.UpdateProvisioner(ctx, prov); err != nil { @@ -366,3 +374,13 @@ func (par *PolicyAdminResponder) DeleteACMEAccountPolicy(w http.ResponseWriter, render.JSONStatus(w, DeleteResponse{Status: "ok"}, http.StatusOK) } + +func applyDefaults(p *linkedca.Policy) { + if p.GetX509() == nil { + return + } + if p.GetX509().VerifySubjectCommonName == nil { + p.X509.VerifySubjectCommonName = &wrapperspb.BoolValue{Value: true} + } + return +} diff --git a/authority/policy/options.go b/authority/policy/options.go index e1c33104..c3b30c0a 100644 --- a/authority/policy/options.go +++ b/authority/policy/options.go @@ -30,6 +30,8 @@ func (o *Options) GetSSHOptions() *SSHPolicyOptions { type X509PolicyOptionsInterface interface { GetAllowedNameOptions() *X509NameOptions GetDeniedNameOptions() *X509NameOptions + IsWildcardLiteralAllowed() bool + ShouldVerifySubjectCommonName() bool } // X509PolicyOptions is a container for x509 allowed and denied @@ -39,6 +41,13 @@ type X509PolicyOptions struct { AllowedNames *X509NameOptions `json:"allow,omitempty"` // DeniedNames contains the x509 denied names DeniedNames *X509NameOptions `json:"deny,omitempty"` + // AllowWildcardLiteral indicates if literal wildcard names + // such as *.example.com and @example.com are allowed. Defaults + // to false. + AllowWildcardLiteral *bool `json:"allow_wildcard_literal,omitempty"` + // VerifySubjectCommonName indicates if the Subject Common Name + // is verified in addition to the SANs. Defaults to true. + VerifySubjectCommonName *bool `json:"verify_subject_common_name,omitempty"` } // X509NameOptions models the X509 name policy configuration. @@ -58,6 +67,43 @@ func (o *X509NameOptions) HasNames() bool { len(o.URIDomains) > 0 } +// GetDeniedNameOptions returns the x509 denied name policy configuration +func (o *X509PolicyOptions) GetDeniedNameOptions() *X509NameOptions { + if o == nil { + return nil + } + return o.DeniedNames +} + +// GetAllowedUserNameOptions returns the SSH allowed user name policy +// configuration. +func (o *SSHPolicyOptions) GetAllowedUserNameOptions() *SSHNameOptions { + if o == nil { + return nil + } + if o.User == nil { + return nil + } + return o.User.AllowedNames +} + +func (o *X509PolicyOptions) IsWildcardLiteralAllowed() bool { + if o == nil { + return true + } + return o.AllowWildcardLiteral != nil && *o.AllowWildcardLiteral +} + +func (o *X509PolicyOptions) ShouldVerifySubjectCommonName() bool { + if o == nil { + return false + } + if o.VerifySubjectCommonName == nil { + return true + } + return *o.VerifySubjectCommonName +} + // SSHPolicyOptionsInterface is an interface for providers of // SSH user and host name policy configuration. type SSHPolicyOptionsInterface interface { @@ -84,26 +130,6 @@ func (o *X509PolicyOptions) GetAllowedNameOptions() *X509NameOptions { return o.AllowedNames } -// GetDeniedNameOptions returns the x509 denied name policy configuration -func (o *X509PolicyOptions) GetDeniedNameOptions() *X509NameOptions { - if o == nil { - return nil - } - return o.DeniedNames -} - -// GetAllowedUserNameOptions returns the SSH allowed user name policy -// configuration. -func (o *SSHPolicyOptions) GetAllowedUserNameOptions() *SSHNameOptions { - if o == nil { - return nil - } - if o.User == nil { - return nil - } - return o.User.AllowedNames -} - // GetDeniedUserNameOptions returns the SSH denied user name policy // configuration. func (o *SSHPolicyOptions) GetDeniedUserNameOptions() *SSHNameOptions { diff --git a/authority/policy/policy.go b/authority/policy/policy.go index 403ac0b7..f1142ea7 100644 --- a/authority/policy/policy.go +++ b/authority/policy/policy.go @@ -50,8 +50,13 @@ func NewX509PolicyEngine(policyOptions X509PolicyOptionsInterface) (X509Policy, return nil, nil } - // enable x509 Subject Common Name validation by default - options = append(options, policy.WithSubjectCommonNameVerification()) + if policyOptions.ShouldVerifySubjectCommonName() { + options = append(options, policy.WithSubjectCommonNameVerification()) + } + + if policyOptions.IsWildcardLiteralAllowed() { + options = append(options, policy.WithAllowLiteralWildcardNames()) + } return policy.New(options...) } diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index 0975a4c2..12e371a6 100644 --- a/authority/provisioner/options.go +++ b/authority/provisioner/options.go @@ -65,6 +65,14 @@ type X509Options struct { // DeniedNames contains the SANs the provisioner is not authorized to sign DeniedNames *policy.X509NameOptions `json:"-"` + + // AllowWildcardLiteral indicates if literal wildcard names + // such as *.example.com and @example.com are allowed. Defaults + // to false. + AllowWildcardLiteral *bool `json:"-"` + // VerifySubjectCommonName indicates if the Subject Common Name + // is verified in addition to the SANs. Defaults to true. + VerifySubjectCommonName *bool `json:"-"` } // HasTemplate returns true if a template is defined in the provisioner options. @@ -90,6 +98,23 @@ func (o *X509Options) GetDeniedNameOptions() *policy.X509NameOptions { return o.DeniedNames } +func (o *X509Options) IsWildcardLiteralAllowed() bool { + if o == nil { + return true + } + return o.AllowWildcardLiteral != nil && *o.AllowWildcardLiteral +} + +func (o *X509Options) ShouldVerifySubjectCommonName() bool { + if o == nil { + return false + } + if o.VerifySubjectCommonName == nil { + return true + } + return *o.VerifySubjectCommonName +} + // 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, diff --git a/go.mod b/go.mod index c8b8c66c..68e384ca 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/go-kit/kit v0.10.0 // indirect github.com/go-piv/piv-go v1.7.0 github.com/golang/mock v1.6.0 + github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.7 github.com/google/uuid v1.3.0 github.com/googleapis/gax-go/v2 v2.1.1 @@ -52,4 +53,4 @@ require ( // replace github.com/smallstep/nosql => ../nosql // replace go.step.sm/crypto => ../crypto // replace go.step.sm/cli-utils => ../cli-utils -// replace go.step.sm/linkedca => ../linkedca +replace go.step.sm/linkedca => ../linkedca