Improve test case and code coverage
This commit is contained in:
parent
91d51c2b88
commit
6bc301339f
5 changed files with 3343 additions and 710 deletions
|
@ -13,14 +13,14 @@ func newX509PolicyEngine(x509Opts *X509Options) (*x509policy.NamePolicyEngine, e
|
|||
}
|
||||
|
||||
options := []x509policy.NamePolicyOption{
|
||||
x509policy.WithEnableSubjectCommonNameVerification(), // enable x509 Subject Common Name validation by default
|
||||
x509policy.WithSubjectCommonNameVerification(), // enable x509 Subject Common Name validation by default
|
||||
}
|
||||
|
||||
allowed := x509Opts.GetAllowedNameOptions()
|
||||
if allowed != nil && allowed.HasNames() {
|
||||
options = append(options,
|
||||
x509policy.WithPermittedDNSDomains(allowed.DNSDomains), // TODO(hs): be a bit more lenient w.r.t. the format of domains? I.e. allow "*.localhost" instead of the ".localhost", which is what Name Constraints do.
|
||||
x509policy.WithPermittedCIDRs(allowed.IPRanges), // TODO(hs): support IPs in addition to ranges
|
||||
x509policy.WithPermittedDNSDomains(allowed.DNSDomains),
|
||||
x509policy.WithPermittedCIDRs(allowed.IPRanges), // TODO(hs): support IPs in addition to ranges
|
||||
x509policy.WithPermittedEmailAddresses(allowed.EmailAddresses),
|
||||
x509policy.WithPermittedURIDomains(allowed.URIDomains),
|
||||
)
|
||||
|
@ -29,8 +29,8 @@ func newX509PolicyEngine(x509Opts *X509Options) (*x509policy.NamePolicyEngine, e
|
|||
denied := x509Opts.GetDeniedNameOptions()
|
||||
if denied != nil && denied.HasNames() {
|
||||
options = append(options,
|
||||
x509policy.WithExcludedDNSDomains(denied.DNSDomains), // TODO(hs): be a bit more lenient w.r.t. the format of domains? I.e. allow "*.localhost" instead of the ".localhost", which is what Name Constraints do.
|
||||
x509policy.WithExcludedCIDRs(denied.IPRanges), // TODO(hs): support IPs in addition to ranges
|
||||
x509policy.WithExcludedDNSDomains(denied.DNSDomains),
|
||||
x509policy.WithExcludedCIDRs(denied.IPRanges), // TODO(hs): support IPs in addition to ranges
|
||||
x509policy.WithExcludedEmailAddresses(denied.EmailAddresses),
|
||||
x509policy.WithExcludedURIDomains(denied.URIDomains),
|
||||
)
|
||||
|
|
|
@ -12,97 +12,120 @@ type NamePolicyOption func(e *NamePolicyEngine) error
|
|||
|
||||
// TODO: wrap (more) errors; and prove a set of known (exported) errors
|
||||
|
||||
func WithEnableSubjectCommonNameVerification() NamePolicyOption {
|
||||
func WithSubjectCommonNameVerification() NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
e.verifySubjectCommonName = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithAllowLiteralWildcardNames() NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
e.allowLiteralWildcardNames = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPermittedDNSDomains(domains []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, domain := range domains {
|
||||
if err := validateDNSDomainConstraint(domain); err != nil {
|
||||
normalizedDomains := make([]string, len(domains))
|
||||
for i, domain := range domains {
|
||||
normalizedDomain, err := normalizeAndValidateDNSDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted domain constraint %q", domain)
|
||||
}
|
||||
normalizedDomains[i] = normalizedDomain
|
||||
}
|
||||
e.permittedDNSDomains = domains
|
||||
e.permittedDNSDomains = normalizedDomains
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddPermittedDNSDomains(domains []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, domain := range domains {
|
||||
if err := validateDNSDomainConstraint(domain); err != nil {
|
||||
normalizedDomains := make([]string, len(domains))
|
||||
for i, domain := range domains {
|
||||
normalizedDomain, err := normalizeAndValidateDNSDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted domain constraint %q", domain)
|
||||
}
|
||||
normalizedDomains[i] = normalizedDomain
|
||||
}
|
||||
e.permittedDNSDomains = append(e.permittedDNSDomains, domains...)
|
||||
e.permittedDNSDomains = append(e.permittedDNSDomains, normalizedDomains...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithExcludedDNSDomains(domains []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, domain := range domains {
|
||||
if err := validateDNSDomainConstraint(domain); err != nil {
|
||||
return errors.Errorf("cannot parse excluded domain constraint %q", domain)
|
||||
normalizedDomains := make([]string, len(domains))
|
||||
for i, domain := range domains {
|
||||
normalizedDomain, err := normalizeAndValidateDNSDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted domain constraint %q", domain)
|
||||
}
|
||||
normalizedDomains[i] = normalizedDomain
|
||||
}
|
||||
e.excludedDNSDomains = domains
|
||||
e.excludedDNSDomains = normalizedDomains
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddExcludedDNSDomains(domains []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, domain := range domains {
|
||||
if err := validateDNSDomainConstraint(domain); err != nil {
|
||||
return errors.Errorf("cannot parse excluded domain constraint %q", domain)
|
||||
normalizedDomains := make([]string, len(domains))
|
||||
for i, domain := range domains {
|
||||
normalizedDomain, err := normalizeAndValidateDNSDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted domain constraint %q", domain)
|
||||
}
|
||||
normalizedDomains[i] = normalizedDomain
|
||||
}
|
||||
e.excludedDNSDomains = append(e.excludedDNSDomains, domains...)
|
||||
e.excludedDNSDomains = append(e.excludedDNSDomains, normalizedDomains...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPermittedDNSDomain(domain string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateDNSDomainConstraint(domain); err != nil {
|
||||
normalizedDomain, err := normalizeAndValidateDNSDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted domain constraint %q", domain)
|
||||
}
|
||||
e.permittedDNSDomains = []string{domain}
|
||||
e.permittedDNSDomains = []string{normalizedDomain}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddPermittedDNSDomain(domain string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateDNSDomainConstraint(domain); err != nil {
|
||||
normalizedDomain, err := normalizeAndValidateDNSDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted domain constraint %q", domain)
|
||||
}
|
||||
e.permittedDNSDomains = append(e.permittedDNSDomains, domain)
|
||||
e.permittedDNSDomains = append(e.permittedDNSDomains, normalizedDomain)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithExcludedDNSDomain(domain string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateDNSDomainConstraint(domain); err != nil {
|
||||
return errors.Errorf("cannot parse excluded domain constraint %q", domain)
|
||||
normalizedDomain, err := normalizeAndValidateDNSDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted domain constraint %q", domain)
|
||||
}
|
||||
e.excludedDNSDomains = []string{domain}
|
||||
e.excludedDNSDomains = []string{normalizedDomain}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddExcludedDNSDomain(domain string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateDNSDomainConstraint(domain); err != nil {
|
||||
return errors.Errorf("cannot parse excluded domain constraint %q", domain)
|
||||
normalizedDomain, err := normalizeAndValidateDNSDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted domain constraint %q", domain)
|
||||
}
|
||||
e.excludedDNSDomains = append(e.excludedDNSDomains, domain)
|
||||
e.excludedDNSDomains = append(e.excludedDNSDomains, normalizedDomain)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -123,13 +146,13 @@ func AddPermittedIPRanges(ipRanges []*net.IPNet) NamePolicyOption {
|
|||
|
||||
func WithPermittedCIDRs(cidrs []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
networks := []*net.IPNet{}
|
||||
for _, cidr := range cidrs {
|
||||
networks := make([]*net.IPNet, len(cidrs))
|
||||
for i, cidr := range cidrs {
|
||||
_, nw, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted CIDR constraint %q", cidr)
|
||||
}
|
||||
networks = append(networks, nw)
|
||||
networks[i] = nw
|
||||
}
|
||||
e.permittedIPRanges = networks
|
||||
return nil
|
||||
|
@ -138,13 +161,13 @@ func WithPermittedCIDRs(cidrs []string) NamePolicyOption {
|
|||
|
||||
func AddPermittedCIDRs(cidrs []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
networks := []*net.IPNet{}
|
||||
for _, cidr := range cidrs {
|
||||
networks := make([]*net.IPNet, len(cidrs))
|
||||
for i, cidr := range cidrs {
|
||||
_, nw, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse permitted CIDR constraint %q", cidr)
|
||||
}
|
||||
networks = append(networks, nw)
|
||||
networks[i] = nw
|
||||
}
|
||||
e.permittedIPRanges = append(e.permittedIPRanges, networks...)
|
||||
return nil
|
||||
|
@ -153,13 +176,13 @@ func AddPermittedCIDRs(cidrs []string) NamePolicyOption {
|
|||
|
||||
func WithExcludedCIDRs(cidrs []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
networks := []*net.IPNet{}
|
||||
for _, cidr := range cidrs {
|
||||
networks := make([]*net.IPNet, len(cidrs))
|
||||
for i, cidr := range cidrs {
|
||||
_, nw, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse excluded CIDR constraint %q", cidr)
|
||||
}
|
||||
networks = append(networks, nw)
|
||||
networks[i] = nw
|
||||
}
|
||||
e.excludedIPRanges = networks
|
||||
return nil
|
||||
|
@ -168,13 +191,13 @@ func WithExcludedCIDRs(cidrs []string) NamePolicyOption {
|
|||
|
||||
func AddExcludedCIDRs(cidrs []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
networks := []*net.IPNet{}
|
||||
for _, cidr := range cidrs {
|
||||
networks := make([]*net.IPNet, len(cidrs))
|
||||
for i, cidr := range cidrs {
|
||||
_, nw, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return errors.Errorf("cannot parse excluded CIDR constraint %q", cidr)
|
||||
}
|
||||
networks = append(networks, nw)
|
||||
networks[i] = nw
|
||||
}
|
||||
e.excludedIPRanges = append(e.excludedIPRanges, networks...)
|
||||
return nil
|
||||
|
@ -309,205 +332,269 @@ func AddExcludedIP(ip net.IP) NamePolicyOption {
|
|||
|
||||
func WithPermittedEmailAddresses(emailAddresses []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, email := range emailAddresses {
|
||||
if err := validateEmailConstraint(email); err != nil {
|
||||
normalizedEmailAddresses := make([]string, len(emailAddresses))
|
||||
for i, email := range emailAddresses {
|
||||
normalizedEmailAddress, err := normalizeAndValidateEmailConstraint(email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizedEmailAddresses[i] = normalizedEmailAddress
|
||||
}
|
||||
e.permittedEmailAddresses = emailAddresses
|
||||
e.permittedEmailAddresses = normalizedEmailAddresses
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddPermittedEmailAddresses(emailAddresses []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, email := range emailAddresses {
|
||||
if err := validateEmailConstraint(email); err != nil {
|
||||
normalizedEmailAddresses := make([]string, len(emailAddresses))
|
||||
for i, email := range emailAddresses {
|
||||
normalizedEmailAddress, err := normalizeAndValidateEmailConstraint(email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizedEmailAddresses[i] = normalizedEmailAddress
|
||||
}
|
||||
e.permittedEmailAddresses = append(e.permittedEmailAddresses, emailAddresses...)
|
||||
e.permittedEmailAddresses = append(e.permittedEmailAddresses, normalizedEmailAddresses...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithExcludedEmailAddresses(emailAddresses []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, email := range emailAddresses {
|
||||
if err := validateEmailConstraint(email); err != nil {
|
||||
normalizedEmailAddresses := make([]string, len(emailAddresses))
|
||||
for i, email := range emailAddresses {
|
||||
normalizedEmailAddress, err := normalizeAndValidateEmailConstraint(email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizedEmailAddresses[i] = normalizedEmailAddress
|
||||
}
|
||||
e.excludedEmailAddresses = emailAddresses
|
||||
e.excludedEmailAddresses = normalizedEmailAddresses
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddExcludedEmailAddresses(emailAddresses []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, email := range emailAddresses {
|
||||
if err := validateEmailConstraint(email); err != nil {
|
||||
normalizedEmailAddresses := make([]string, len(emailAddresses))
|
||||
for i, email := range emailAddresses {
|
||||
normalizedEmailAddress, err := normalizeAndValidateEmailConstraint(email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizedEmailAddresses[i] = normalizedEmailAddress
|
||||
}
|
||||
e.excludedEmailAddresses = append(e.excludedEmailAddresses, emailAddresses...)
|
||||
e.excludedEmailAddresses = append(e.excludedEmailAddresses, normalizedEmailAddresses...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPermittedEmailAddress(emailAddress string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateEmailConstraint(emailAddress); err != nil {
|
||||
normalizedEmailAddress, err := normalizeAndValidateEmailConstraint(emailAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.permittedEmailAddresses = []string{emailAddress}
|
||||
e.permittedEmailAddresses = []string{normalizedEmailAddress}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddPermittedEmailAddress(emailAddress string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateEmailConstraint(emailAddress); err != nil {
|
||||
normalizedEmailAddress, err := normalizeAndValidateEmailConstraint(emailAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.permittedEmailAddresses = append(e.permittedEmailAddresses, emailAddress)
|
||||
e.permittedEmailAddresses = append(e.permittedEmailAddresses, normalizedEmailAddress)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithExcludedEmailAddress(emailAddress string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateEmailConstraint(emailAddress); err != nil {
|
||||
normalizedEmailAddress, err := normalizeAndValidateEmailConstraint(emailAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.excludedEmailAddresses = []string{emailAddress}
|
||||
e.excludedEmailAddresses = []string{normalizedEmailAddress}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddExcludedEmailAddress(emailAddress string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateEmailConstraint(emailAddress); err != nil {
|
||||
normalizedEmailAddress, err := normalizeAndValidateEmailConstraint(emailAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.excludedEmailAddresses = append(e.excludedEmailAddresses, emailAddress)
|
||||
e.excludedEmailAddresses = append(e.excludedEmailAddresses, normalizedEmailAddress)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPermittedURIDomains(uriDomains []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, domain := range uriDomains {
|
||||
if err := validateURIDomainConstraint(domain); err != nil {
|
||||
normalizedURIDomains := make([]string, len(uriDomains))
|
||||
for i, domain := range uriDomains {
|
||||
normalizedURIDomain, err := normalizeAndValidateURIDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizedURIDomains[i] = normalizedURIDomain
|
||||
}
|
||||
e.permittedURIDomains = uriDomains
|
||||
e.permittedURIDomains = normalizedURIDomains
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddPermittedURIDomains(uriDomains []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, domain := range uriDomains {
|
||||
if err := validateURIDomainConstraint(domain); err != nil {
|
||||
normalizedURIDomains := make([]string, len(uriDomains))
|
||||
for i, domain := range uriDomains {
|
||||
normalizedURIDomain, err := normalizeAndValidateURIDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizedURIDomains[i] = normalizedURIDomain
|
||||
}
|
||||
e.permittedURIDomains = append(e.permittedURIDomains, uriDomains...)
|
||||
e.permittedURIDomains = append(e.permittedURIDomains, normalizedURIDomains...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPermittedURIDomain(uriDomain string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateURIDomainConstraint(uriDomain); err != nil {
|
||||
normalizedURIDomain, err := normalizeAndValidateURIDomainConstraint(uriDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.permittedURIDomains = []string{uriDomain}
|
||||
e.permittedURIDomains = []string{normalizedURIDomain}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddPermittedURIDomain(uriDomain string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateURIDomainConstraint(uriDomain); err != nil {
|
||||
normalizedURIDomain, err := normalizeAndValidateURIDomainConstraint(uriDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.permittedURIDomains = append(e.permittedURIDomains, uriDomain)
|
||||
e.permittedURIDomains = append(e.permittedURIDomains, normalizedURIDomain)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithExcludedURIDomains(uriDomains []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, domain := range uriDomains {
|
||||
if err := validateURIDomainConstraint(domain); err != nil {
|
||||
normalizedURIDomains := make([]string, len(uriDomains))
|
||||
for i, domain := range uriDomains {
|
||||
normalizedURIDomain, err := normalizeAndValidateURIDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizedURIDomains[i] = normalizedURIDomain
|
||||
}
|
||||
e.excludedURIDomains = uriDomains
|
||||
e.excludedURIDomains = normalizedURIDomains
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddExcludedURIDomains(uriDomains []string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
for _, domain := range uriDomains {
|
||||
if err := validateURIDomainConstraint(domain); err != nil {
|
||||
normalizedURIDomains := make([]string, len(uriDomains))
|
||||
for i, domain := range uriDomains {
|
||||
normalizedURIDomain, err := normalizeAndValidateURIDomainConstraint(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
normalizedURIDomains[i] = normalizedURIDomain
|
||||
}
|
||||
e.excludedURIDomains = append(e.excludedURIDomains, uriDomains...)
|
||||
e.excludedURIDomains = append(e.excludedURIDomains, normalizedURIDomains...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithExcludedURIDomain(uriDomain string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateURIDomainConstraint(uriDomain); err != nil {
|
||||
normalizedURIDomain, err := normalizeAndValidateURIDomainConstraint(uriDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.excludedURIDomains = []string{uriDomain}
|
||||
e.excludedURIDomains = []string{normalizedURIDomain}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddExcludedURIDomain(uriDomain string) NamePolicyOption {
|
||||
return func(e *NamePolicyEngine) error {
|
||||
if err := validateURIDomainConstraint(uriDomain); err != nil {
|
||||
normalizedURIDomain, err := normalizeAndValidateURIDomainConstraint(uriDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.excludedURIDomains = append(e.excludedURIDomains, uriDomain)
|
||||
e.excludedURIDomains = append(e.excludedURIDomains, normalizedURIDomain)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func validateDNSDomainConstraint(domain string) error {
|
||||
if _, ok := domainToReverseLabels(domain); !ok {
|
||||
return errors.Errorf("cannot parse permitted domain constraint %q", domain)
|
||||
func normalizeAndValidateDNSDomainConstraint(constraint string) (string, error) {
|
||||
normalizedConstraint := strings.TrimSpace(constraint)
|
||||
if strings.Contains(normalizedConstraint, "..") {
|
||||
return "", errors.Errorf("domain constraint %q cannot have empty labels", constraint)
|
||||
}
|
||||
return nil
|
||||
if strings.HasPrefix(normalizedConstraint, "*.") {
|
||||
normalizedConstraint = normalizedConstraint[1:] // cut off wildcard character; keep the period
|
||||
}
|
||||
if strings.Contains(normalizedConstraint, "*") {
|
||||
return "", errors.Errorf("domain constraint %q can only have wildcard as starting character", constraint)
|
||||
}
|
||||
if _, ok := domainToReverseLabels(normalizedConstraint); !ok {
|
||||
return "", errors.Errorf("cannot parse permitted domain constraint %q", constraint)
|
||||
}
|
||||
return normalizedConstraint, nil
|
||||
}
|
||||
|
||||
func validateEmailConstraint(constraint string) error {
|
||||
if strings.Contains(constraint, "@") {
|
||||
_, ok := parseRFC2821Mailbox(constraint)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot parse email constraint %q", constraint)
|
||||
func normalizeAndValidateEmailConstraint(constraint string) (string, error) {
|
||||
normalizedConstraint := strings.TrimSpace(constraint)
|
||||
if strings.Contains(normalizedConstraint, "*") {
|
||||
return "", fmt.Errorf("email constraint %q cannot contain asterisk", constraint)
|
||||
}
|
||||
if strings.Count(normalizedConstraint, "@") > 1 {
|
||||
return "", fmt.Errorf("email constraint %q contains too many @ characters", constraint)
|
||||
}
|
||||
if normalizedConstraint[0] == '@' {
|
||||
normalizedConstraint = normalizedConstraint[1:] // remove the leading @ as wildcard for emails
|
||||
}
|
||||
if normalizedConstraint[0] == '.' {
|
||||
return "", fmt.Errorf("email constraint %q cannot start with period", constraint)
|
||||
}
|
||||
if strings.Contains(normalizedConstraint, "@") {
|
||||
if _, ok := parseRFC2821Mailbox(normalizedConstraint); !ok {
|
||||
return "", fmt.Errorf("cannot parse email constraint %q", constraint)
|
||||
}
|
||||
}
|
||||
_, ok := domainToReverseLabels(constraint)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot parse email domain constraint %q", constraint)
|
||||
if _, ok := domainToReverseLabels(normalizedConstraint); !ok {
|
||||
return "", fmt.Errorf("cannot parse email domain constraint %q", constraint)
|
||||
}
|
||||
return nil
|
||||
return normalizedConstraint, nil
|
||||
}
|
||||
|
||||
func validateURIDomainConstraint(constraint string) error {
|
||||
_, ok := domainToReverseLabels(constraint)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot parse URI domain constraint %q", constraint)
|
||||
func normalizeAndValidateURIDomainConstraint(constraint string) (string, error) {
|
||||
normalizedConstraint := strings.TrimSpace(constraint)
|
||||
if strings.Contains(normalizedConstraint, "..") {
|
||||
return "", errors.Errorf("URI domain constraint %q cannot have empty labels", constraint)
|
||||
}
|
||||
return nil
|
||||
if strings.HasPrefix(normalizedConstraint, "*.") {
|
||||
normalizedConstraint = normalizedConstraint[1:] // cut off wildcard character; keep the period
|
||||
}
|
||||
if strings.Contains(normalizedConstraint, "*") {
|
||||
return "", errors.Errorf("URI domain constraint %q can only have wildcard as starting character", constraint)
|
||||
}
|
||||
// TODO(hs): block constraints that look like IPs too? Because hosts can't be matched to those.
|
||||
_, ok := domainToReverseLabels(normalizedConstraint)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("cannot parse URI domain constraint %q", constraint)
|
||||
}
|
||||
return normalizedConstraint, nil
|
||||
}
|
||||
|
|
1339
policy/x509/options_test.go
Normal file
1339
policy/x509/options_test.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -50,8 +50,13 @@ func (e CertificateInvalidError) Error() string {
|
|||
// TODO(hs): the x509 RFC also defines name checks on directory name; support that?
|
||||
// TODO(hs): implement Stringer interface: describe the contents of the NamePolicyEngine?
|
||||
type NamePolicyEngine struct {
|
||||
options []NamePolicyOption
|
||||
|
||||
// verifySubjectCommonName is set when Subject Common Name must be verified
|
||||
verifySubjectCommonName bool
|
||||
// allowLiteralWildcardNames allows literal wildcard DNS domains
|
||||
allowLiteralWildcardNames bool
|
||||
|
||||
// permitted and exluded constraints similar to x509 Name Constraints
|
||||
permittedDNSDomains []string
|
||||
excludedDNSDomains []string
|
||||
permittedIPRanges []*net.IPNet
|
||||
|
@ -60,22 +65,84 @@ type NamePolicyEngine struct {
|
|||
excludedEmailAddresses []string
|
||||
permittedURIDomains []string
|
||||
excludedURIDomains []string
|
||||
|
||||
// some internal counts for housekeeping
|
||||
numberOfDNSDomainConstraints int
|
||||
numberOfIPRangeConstraints int
|
||||
numberOfEmailAddressConstraints int
|
||||
numberOfURIDomainConstraints int
|
||||
totalNumberOfPermittedConstraints int
|
||||
totalNumberOfExcludedConstraints int
|
||||
totalNumberOfConstraints int
|
||||
}
|
||||
|
||||
// NewNamePolicyEngine creates a new NamePolicyEngine with NamePolicyOptions
|
||||
func New(opts ...NamePolicyOption) (*NamePolicyEngine, error) {
|
||||
|
||||
e := &NamePolicyEngine{}
|
||||
e.options = append(e.options, opts...)
|
||||
for _, option := range e.options {
|
||||
for _, option := range opts {
|
||||
if err := option(e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
e.permittedDNSDomains = removeDuplicates(e.permittedDNSDomains)
|
||||
e.permittedIPRanges = removeDuplicateIPRanges(e.permittedIPRanges)
|
||||
e.permittedEmailAddresses = removeDuplicates(e.permittedEmailAddresses)
|
||||
e.permittedURIDomains = removeDuplicates(e.permittedURIDomains)
|
||||
|
||||
e.excludedDNSDomains = removeDuplicates(e.excludedDNSDomains)
|
||||
e.excludedIPRanges = removeDuplicateIPRanges(e.excludedIPRanges)
|
||||
e.excludedEmailAddresses = removeDuplicates(e.excludedEmailAddresses)
|
||||
e.excludedURIDomains = removeDuplicates(e.excludedURIDomains)
|
||||
|
||||
e.numberOfDNSDomainConstraints = len(e.permittedDNSDomains) + len(e.excludedDNSDomains)
|
||||
e.numberOfIPRangeConstraints = len(e.permittedIPRanges) + len(e.excludedIPRanges)
|
||||
e.numberOfEmailAddressConstraints = len(e.permittedEmailAddresses) + len(e.excludedEmailAddresses)
|
||||
e.numberOfURIDomainConstraints = len(e.permittedURIDomains) + len(e.excludedURIDomains)
|
||||
|
||||
e.totalNumberOfPermittedConstraints = len(e.permittedDNSDomains) + len(e.permittedIPRanges) +
|
||||
len(e.permittedEmailAddresses) + len(e.permittedURIDomains)
|
||||
|
||||
e.totalNumberOfExcludedConstraints = len(e.excludedDNSDomains) + len(e.excludedIPRanges) +
|
||||
len(e.excludedEmailAddresses) + len(e.excludedURIDomains)
|
||||
|
||||
e.totalNumberOfConstraints = e.totalNumberOfPermittedConstraints + e.totalNumberOfExcludedConstraints
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func removeDuplicates(strSlice []string) []string {
|
||||
if len(strSlice) == 0 {
|
||||
return nil
|
||||
}
|
||||
keys := make(map[string]bool)
|
||||
result := []string{}
|
||||
for _, item := range strSlice {
|
||||
if _, value := keys[item]; !value {
|
||||
keys[item] = true
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func removeDuplicateIPRanges(ipRanges []*net.IPNet) []*net.IPNet {
|
||||
if len(ipRanges) == 0 {
|
||||
return nil
|
||||
}
|
||||
keys := make(map[string]bool)
|
||||
result := []*net.IPNet{}
|
||||
for _, item := range ipRanges {
|
||||
key := item.String()
|
||||
if _, value := keys[key]; !value {
|
||||
keys[key] = true
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// AreCertificateNamesAllowed verifies that all SANs in a Certificate are allowed.
|
||||
func (e *NamePolicyEngine) AreCertificateNamesAllowed(cert *x509.Certificate) (bool, error) {
|
||||
dnsNames, ips, emails, uris := cert.DNSNames, cert.IPAddresses, cert.EmailAddresses, cert.URIs
|
||||
|
@ -155,28 +222,51 @@ func appendSubjectCommonName(subject pkix.Name, dnsNames *[]string, ips *[]net.I
|
|||
// in https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/crypto/x509/verify.go
|
||||
func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailAddresses []string, uris []*url.URL) error {
|
||||
|
||||
// TODO: return our own type of error?
|
||||
// nothing to compare against; return early
|
||||
if e.totalNumberOfConstraints == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: set limit on total of all names? In x509 there's a limit on the number of comparisons
|
||||
// TODO: return our own type(s) of error?
|
||||
// TODO: implement check that requires at least a single name in all of the SANs + subject?
|
||||
|
||||
// TODO: set limit on total of all names validated? In x509 there's a limit on the number of comparisons
|
||||
// that protects the CA from a DoS (i.e. many heavy comparisons). The x509 implementation takes
|
||||
// this number as a total of all checks and keeps a (pointer to a) counter of the number of checks
|
||||
// executed so far.
|
||||
|
||||
// TODO: implement matching URI schemes, paths, etc; not just the domain
|
||||
|
||||
// TODO: gather all errors, or return early? Currently we return early on the first wrong name; check might fail for multiple names.
|
||||
// Perhaps make that an option?
|
||||
for _, dns := range dnsNames {
|
||||
// if there are DNS names to check, no DNS constraints set, but there are other permitted constraints,
|
||||
// then return error, because DNS should be explicitly configured to be allowed in that case. In case there are
|
||||
// (other) excluded constraints, we'll allow a DNS (implicit allow; currently).
|
||||
if e.numberOfDNSDomainConstraints == 0 && e.totalNumberOfPermittedConstraints > 0 {
|
||||
return CertificateInvalidError{
|
||||
Reason: x509.CANotAuthorizedForThisName,
|
||||
Detail: fmt.Sprintf("dns %q is not permitted by any constraint", dns), // TODO(hs): change this error (message)
|
||||
}
|
||||
}
|
||||
if _, ok := domainToReverseLabels(dns); !ok {
|
||||
return errors.Errorf("cannot parse dns %q", dns)
|
||||
}
|
||||
if err := checkNameConstraints("dns", dns, dns,
|
||||
func(parsedName, constraint interface{}) (bool, error) {
|
||||
return matchDomainConstraint(parsedName.(string), constraint.(string))
|
||||
return e.matchDomainConstraint(parsedName.(string), constraint.(string))
|
||||
}, e.permittedDNSDomains, e.excludedDNSDomains); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
if e.numberOfIPRangeConstraints == 0 && e.totalNumberOfPermittedConstraints > 0 {
|
||||
return CertificateInvalidError{
|
||||
Reason: x509.CANotAuthorizedForThisName,
|
||||
Detail: fmt.Sprintf("ip %q is not permitted by any constraint", ip.String()),
|
||||
}
|
||||
}
|
||||
if err := checkNameConstraints("ip", ip.String(), ip,
|
||||
func(parsedName, constraint interface{}) (bool, error) {
|
||||
return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet))
|
||||
|
@ -186,22 +276,34 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA
|
|||
}
|
||||
|
||||
for _, email := range emailAddresses {
|
||||
if e.numberOfEmailAddressConstraints == 0 && e.totalNumberOfPermittedConstraints > 0 {
|
||||
return CertificateInvalidError{
|
||||
Reason: x509.CANotAuthorizedForThisName,
|
||||
Detail: fmt.Sprintf("email %q is not permitted by any constraint", email),
|
||||
}
|
||||
}
|
||||
mailbox, ok := parseRFC2821Mailbox(email)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot parse rfc822Name %q", mailbox)
|
||||
}
|
||||
if err := checkNameConstraints("email", email, mailbox,
|
||||
func(parsedName, constraint interface{}) (bool, error) {
|
||||
return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string))
|
||||
return e.matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string))
|
||||
}, e.permittedEmailAddresses, e.excludedEmailAddresses); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, uri := range uris {
|
||||
if e.numberOfURIDomainConstraints == 0 && e.totalNumberOfPermittedConstraints > 0 {
|
||||
return CertificateInvalidError{
|
||||
Reason: x509.CANotAuthorizedForThisName,
|
||||
Detail: fmt.Sprintf("uri %q is not permitted by any constraint", uri.String()),
|
||||
}
|
||||
}
|
||||
if err := checkNameConstraints("uri", uri.String(), uri,
|
||||
func(parsedName, constraint interface{}) (bool, error) {
|
||||
return matchURIConstraint(parsedName.(*url.URL), constraint.(string))
|
||||
return e.matchURIConstraint(parsedName.(*url.URL), constraint.(string))
|
||||
}, e.permittedURIDomains, e.excludedURIDomains); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -217,11 +319,10 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA
|
|||
return nil
|
||||
}
|
||||
|
||||
// checkNameConstraints checks that c permits a child certificate to claim the
|
||||
// given name, of type nameType. The argument parsedName contains the parsed
|
||||
// form of name, suitable for passing to the match function. The total number
|
||||
// of comparisons is tracked in the given count and should not exceed the given
|
||||
// limit.
|
||||
// checkNameConstraints checks that a name, of type nameType is permitted.
|
||||
// The argument parsedName contains the parsed form of name, suitable for passing
|
||||
// to the match function. The total number of comparisons is tracked in the given
|
||||
// count and should not exceed the given limit.
|
||||
// SOURCE: https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/crypto/x509/verify.go
|
||||
func checkNameConstraints(
|
||||
nameType string,
|
||||
|
@ -476,13 +577,44 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
|
|||
}
|
||||
|
||||
// SOURCE: https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/crypto/x509/verify.go
|
||||
func matchDomainConstraint(domain, constraint string) (bool, error) {
|
||||
func (e *NamePolicyEngine) matchDomainConstraint(domain, constraint string) (bool, error) {
|
||||
// The meaning of zero length constraints is not specified, but this
|
||||
// code follows NSS and accepts them as matching everything.
|
||||
if constraint == "" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// A single whitespace seems to be considered a valid domain, but we don't allow it.
|
||||
if domain == " " {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Block domains that start with just a period
|
||||
// TODO(hs): check if we should allow domains starting with "." at all; not sure if this is allowed in x509 names and certs.
|
||||
if domain[0] == '.' {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Block wildcard domains that don't start with exactly "*." (i.e. double wildcards and such)
|
||||
if domain[0] == '*' && domain[1] != '.' {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Check if the domain starts with a wildcard and return early if not allowed
|
||||
if strings.HasPrefix(domain, "*.") && !e.allowLiteralWildcardNames {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Only allow asterisk at the start of the domain; we don't allow them as part of a domain label or as a (sub)domain label (currently)
|
||||
if strings.LastIndex(domain, "*") > 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Don't allow constraints with empty labels in any position
|
||||
if strings.Contains(constraint, "..") {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
domainLabels, ok := domainToReverseLabels(domain)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("cannot parse domain %q", domain)
|
||||
|
@ -491,7 +623,9 @@ func matchDomainConstraint(domain, constraint string) (bool, error) {
|
|||
// RFC 5280 says that a leading period in a domain name means that at
|
||||
// least one label must be prepended, but only for URI and email
|
||||
// constraints, not DNS constraints. The code also supports that
|
||||
// behavior for DNS constraints.
|
||||
// behavior for DNS constraints. In our adaptation of the original
|
||||
// Go stdlib x509 Name Constraint implementation we look for exactly
|
||||
// one subdomain, currently.
|
||||
|
||||
mustHaveSubdomains := false
|
||||
if constraint[0] == '.' {
|
||||
|
@ -501,11 +635,22 @@ func matchDomainConstraint(domain, constraint string) (bool, error) {
|
|||
|
||||
constraintLabels, ok := domainToReverseLabels(constraint)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("cannot parse domain %q", constraint)
|
||||
return false, fmt.Errorf("cannot parse domain constraint %q", constraint)
|
||||
}
|
||||
|
||||
if len(domainLabels) < len(constraintLabels) ||
|
||||
(mustHaveSubdomains && len(domainLabels) == len(constraintLabels)) {
|
||||
// fmt.Println(mustHaveSubdomains)
|
||||
// fmt.Println(constraintLabels)
|
||||
// fmt.Println(domainLabels)
|
||||
|
||||
expectedNumberOfLabels := len(constraintLabels)
|
||||
if mustHaveSubdomains {
|
||||
// we expect exactly one more label if it starts with the "canonical" x509 "wildcard": "."
|
||||
// in the future we could extend this to support multiple additional labels and/or more
|
||||
// complex matching.
|
||||
expectedNumberOfLabels++
|
||||
}
|
||||
|
||||
if len(domainLabels) != expectedNumberOfLabels {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
@ -552,8 +697,12 @@ func isIPv4(ip net.IP) bool {
|
|||
}
|
||||
|
||||
// SOURCE: https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/crypto/x509/verify.go
|
||||
func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) {
|
||||
// If the constraint contains an @, then it specifies an exact mailbox name.
|
||||
func (e *NamePolicyEngine) matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) {
|
||||
// TODO(hs): handle literal wildcard case for emails? Does that even make sense?
|
||||
// If the constraint contains an @, then it specifies an exact mailbox name (currently)
|
||||
if strings.Contains(constraint, "*") {
|
||||
return false, fmt.Errorf("email constraint %q cannot contain asterisk", constraint)
|
||||
}
|
||||
if strings.Contains(constraint, "@") {
|
||||
constraintMailbox, ok := parseRFC2821Mailbox(constraint)
|
||||
if !ok {
|
||||
|
@ -564,11 +713,11 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, erro
|
|||
|
||||
// Otherwise the constraint is like a DNS constraint of the domain part
|
||||
// of the mailbox.
|
||||
return matchDomainConstraint(mailbox.domain, constraint)
|
||||
return e.matchDomainConstraint(mailbox.domain, constraint)
|
||||
}
|
||||
|
||||
// SOURCE: https://cs.opensource.google/go/go/+/refs/tags/go1.17.5:src/crypto/x509/verify.go
|
||||
func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
|
||||
func (e *NamePolicyEngine) matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
|
||||
// From RFC 5280, Section 4.2.1.10:
|
||||
// “a uniformResourceIdentifier that does not include an authority
|
||||
// component with a host name specified as a fully qualified domain
|
||||
|
@ -582,6 +731,11 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
|
|||
return false, fmt.Errorf("URI with empty host (%q) cannot be matched against constraints", uri.String())
|
||||
}
|
||||
|
||||
// Block hosts with the wildcard character; no exceptions, also not when wildcards allowed.
|
||||
if strings.Contains(host, "*") {
|
||||
return false, fmt.Errorf("URI host %q cannot contain asterisk", uri.String())
|
||||
}
|
||||
|
||||
if strings.Contains(host, ":") && !strings.HasSuffix(host, "]") {
|
||||
var err error
|
||||
host, _, err = net.SplitHostPort(uri.Host)
|
||||
|
@ -592,8 +746,10 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
|
|||
|
||||
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") ||
|
||||
net.ParseIP(host) != nil {
|
||||
return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String())
|
||||
return false, fmt.Errorf("URI with IP %q cannot be matched against constraints", uri.String())
|
||||
}
|
||||
|
||||
return matchDomainConstraint(host, constraint)
|
||||
// TODO(hs): add checks for scheme, path, etc.; either here, or in a different constraint matcher (to keep this one simple)
|
||||
|
||||
return e.matchDomainConstraint(host, constraint)
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue