2b7f6931f3
Subject Common Names can now also be configured to be allowed or denied, similar to SANs. When a Subject Common Name is not explicitly allowed or denied, its type will be determined and its value will be validated according to the constraints for that type of name (i.e. URI).
3378 lines
80 KiB
Go
Executable file
3378 lines
80 KiB
Go
Executable file
package policy
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"errors"
|
|
"net"
|
|
"net/url"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/stretchr/testify/assert"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// TODO(hs): the functionality in the policy engine is a nice candidate for trying fuzzing on
|
|
// TODO(hs): more complex test use cases that combine multiple names and permitted/excluded entries?
|
|
|
|
func TestNamePolicyEngine_matchDomainConstraint(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
allowLiteralWildcardNames bool
|
|
domain string
|
|
constraint string
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "fail/wildcard",
|
|
domain: "host.local",
|
|
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/wildcard-literal",
|
|
domain: "*.example.com",
|
|
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/specific-domain",
|
|
domain: "www.example.com",
|
|
constraint: "host.example.com",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/single-whitespace-domain",
|
|
domain: " ",
|
|
constraint: "host.example.com",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/period-domain",
|
|
domain: ".host.example.com",
|
|
constraint: ".example.com",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/wrong-asterisk-prefix",
|
|
domain: "*Xexample.com",
|
|
constraint: ".example.com",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/asterisk-in-domain",
|
|
domain: "e*ample.com",
|
|
constraint: ".com",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/asterisk-label",
|
|
domain: "example.*.local",
|
|
constraint: ".local",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/multiple-periods",
|
|
domain: "example.local",
|
|
constraint: "..local",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/error-parsing-domain",
|
|
domain: string(byte(0)),
|
|
constraint: ".local",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/error-parsing-constraint",
|
|
domain: "example.local",
|
|
constraint: string(byte(0)),
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/no-subdomain",
|
|
domain: "local",
|
|
constraint: ".local",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/too-many-subdomains",
|
|
domain: "www.example.local",
|
|
constraint: ".local",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/wrong-domain",
|
|
domain: "example.notlocal",
|
|
constraint: ".local",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "false/idna-internationalized-domain-name",
|
|
domain: "JP納豆.例.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
|
constraint: ".例.jp",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "false/idna-internationalized-domain-name-constraint",
|
|
domain: "xn--jp-cd2fp15c.xn--fsq.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
|
constraint: ".例.jp",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "ok/empty-constraint",
|
|
domain: "www.example.com",
|
|
constraint: "",
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/wildcard",
|
|
domain: "www.example.com",
|
|
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/wildcard-literal",
|
|
allowLiteralWildcardNames: true,
|
|
domain: "*.example.com", // specifically allowed using an option on the NamePolicyEngine
|
|
constraint: ".example.com", // internally we're using the x509 period prefix as the indicator for exactly one subdomain
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/specific-domain",
|
|
domain: "www.example.com",
|
|
constraint: "www.example.com",
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/different-case",
|
|
domain: "WWW.EXAMPLE.com",
|
|
constraint: "www.example.com",
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/idna-internationalized-domain-name-punycode",
|
|
domain: "xn--jp-cd2fp15c.xn--fsq.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
|
constraint: ".xn--fsq.jp",
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
engine := NamePolicyEngine{
|
|
allowLiteralWildcardNames: tt.allowLiteralWildcardNames,
|
|
}
|
|
got, err := engine.matchDomainConstraint(tt.domain, tt.constraint)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("NamePolicyEngine.matchDomainConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("NamePolicyEngine.matchDomainConstraint() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_matchIPConstraint(t *testing.T) {
|
|
nat64IP, nat64Net, err := net.ParseCIDR("64:ff9b::/96")
|
|
assert.NoError(t, err)
|
|
tests := []struct {
|
|
name string
|
|
ip net.IP
|
|
constraint *net.IPNet
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "false/ipv4-in-ipv6-nat64",
|
|
ip: net.ParseIP("192.0.2.128"),
|
|
constraint: nat64Net,
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/ipv4",
|
|
ip: net.ParseIP("127.0.0.1"),
|
|
constraint: &net.IPNet{
|
|
IP: net.ParseIP("127.0.0.0"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/ipv6",
|
|
ip: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7335"),
|
|
constraint: &net.IPNet{
|
|
IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
|
Mask: net.CIDRMask(120, 128),
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/ipv4-in-ipv6", // ipv4 in ipv6 addresses are considered the same in the current implementation, because Go parses them as IPv4
|
|
ip: net.ParseIP("::ffff:192.0.2.128"),
|
|
constraint: &net.IPNet{
|
|
IP: net.ParseIP("192.0.2.0"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/ipv4-in-ipv6-nat64-fixed-ip",
|
|
ip: nat64IP,
|
|
constraint: nat64Net,
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/ipv4-in-ipv6-nat64",
|
|
ip: net.ParseIP("64:ff9b::192.0.2.129"),
|
|
constraint: nat64Net,
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := matchIPConstraint(tt.ip, tt.constraint)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("matchIPConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("matchIPConstraint() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNamePolicyEngine_matchEmailConstraint(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
engine *NamePolicyEngine
|
|
mailbox rfc2821Mailbox
|
|
constraint string
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "fail/asterisk-prefix",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: "*@example.com",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/asterisk-label",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: "@host.*.example.com",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/asterisk-inside-local",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: "m*il@local",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/asterisk-inside-domain",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: "@h*st.example.com",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/parse-email",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: "@example.com",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/wildcard",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: "example.com",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/wildcard-x509-period",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: ".local", // "wildcard" for the local domain; requires exactly 1 subdomain
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/specific-mail-wrong-domain",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: "mail@example.com",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/specific-mail-wrong-local",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "root",
|
|
domain: "example.com",
|
|
},
|
|
constraint: "mail@example.com",
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/wildcard",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: "local", // "wildcard" for the local domain
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/wildcard-x509-period",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "example.local",
|
|
},
|
|
constraint: ".local", // "wildcard" for the local domain; requires exactly 1 subdomain
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/specific-mail",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "local",
|
|
},
|
|
constraint: "mail@local",
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/wildcard-tld",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "example.com",
|
|
},
|
|
constraint: "example.com", // "wildcard" for 'example.com'
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/different-case",
|
|
engine: &NamePolicyEngine{},
|
|
mailbox: rfc2821Mailbox{
|
|
local: "mail",
|
|
domain: "EXAMPLE.com",
|
|
},
|
|
constraint: "example.com", // "wildcard" for 'example.com'
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := tt.engine.matchEmailConstraint(tt.mailbox, tt.constraint)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("NamePolicyEngine.matchEmailConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("NamePolicyEngine.matchEmailConstraint() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNamePolicyEngine_matchURIConstraint(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
engine *NamePolicyEngine
|
|
uri *url.URL
|
|
constraint string
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "fail/empty-host",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "",
|
|
},
|
|
constraint: ".local",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/host-with-asterisk-prefix",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "*.local",
|
|
},
|
|
constraint: ".local",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/host-with-asterisk-label",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "host.*.local",
|
|
},
|
|
constraint: ".local",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/host-with-asterisk-inside",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "h*st.local",
|
|
},
|
|
constraint: ".local",
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/wildcard",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "www.example.notlocal",
|
|
},
|
|
constraint: ".example.local", // using x509 period as the "wildcard"; expects a single subdomain
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/wildcard-subdomains-too-deep",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "www.sub.example.local",
|
|
},
|
|
constraint: ".example.local", // using x509 period as the "wildcard"; expects a single subdomain
|
|
want: false,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "fail/host-with-port-split-error",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "www.example.local::8080",
|
|
},
|
|
constraint: ".example.local", // using x509 period as the "wildcard"; expects a single subdomain
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/host-with-ipv4",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "127.0.0.1",
|
|
},
|
|
constraint: ".example.local", // using x509 period as the "wildcard"; expects a single subdomain
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "fail/host-with-ipv6",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
|
|
},
|
|
constraint: ".example.local", // using x509 period as the "wildcard"; expects a single subdomain
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "ok/wildcard",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "www.example.local",
|
|
},
|
|
constraint: ".example.local", // using x509 period as the "wildcard"; expects a single subdomain
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/host-with-port",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "www.example.local:8080",
|
|
},
|
|
constraint: ".example.local", // using x509 period as the "wildcard"; expects a single subdomain
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ok/different-case",
|
|
engine: &NamePolicyEngine{},
|
|
uri: &url.URL{
|
|
Scheme: "https",
|
|
Host: "www.EXAMPLE.local",
|
|
},
|
|
constraint: ".example.local", // using x509 period as the "wildcard"; expects a single subdomain
|
|
want: true,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := tt.engine.matchURIConstraint(tt.uri, tt.constraint)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("NamePolicyEngine.matchURIConstraint() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("NamePolicyEngine.matchURIConstraint() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNamePolicyEngine_X509_AllAllowed(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
options []NamePolicyOption
|
|
cert *x509.Certificate
|
|
want bool
|
|
wantErr *NamePolicyError
|
|
}{
|
|
// SINGLE SAN TYPE PERMITTED FAILURE TESTS
|
|
{
|
|
name: "fail/dns-permitted",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"www.example.com"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "www.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-wildcard-literal-x509",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.x509local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{
|
|
"*.x509local",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "*.x509local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-single-host",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("host.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"differenthost.local"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "differenthost.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-no-label",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"local"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-empty-label",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"www..local"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotParseDomain,
|
|
NameType: DNSNameType,
|
|
Name: "www..local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-dot-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{
|
|
".local",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: ".local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-wildcard-multiple-subdomains",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{
|
|
"sub.example.local",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "sub.example.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-wildcard-literal",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{
|
|
"*.local",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "*.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-idna-internationalized-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.豆.jp"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{
|
|
string(byte(0)) + ".例.jp",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotParseDomain,
|
|
NameType: DNSNameType,
|
|
Name: string(byte(0)) + ".例.jp",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/ipv4-permitted",
|
|
options: []NamePolicyOption{
|
|
WithPermittedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("1.1.1.1")},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: IPNameType,
|
|
Name: "1.1.1.1",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/ipv6-permitted",
|
|
options: []NamePolicyOption{
|
|
WithPermittedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
|
Mask: net.CIDRMask(120, 128),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("3001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: IPNameType,
|
|
Name: "3001:db8:85a3::8a2e:370:7334", // IPv6 is shortened internally
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-wildcard",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{
|
|
"test@local.com",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "test@local.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-wildcard-x509",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{
|
|
"test@local.com",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "test@local.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-specific-mailbox",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("test@local.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{
|
|
"root@local.com",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "root@local.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-wildcard-subdomain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{
|
|
"test@sub.example.com",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "test@sub.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-idna-internationalized-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@例.jp"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"bücher@例.jp"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotParseRFC822Name,
|
|
NameType: EmailNameType,
|
|
Name: "bücher@例.jp",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-idna-internationalized-domain-rfc822",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@例.jp"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"bücher@例.jp" + string(byte(0))},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotParseRFC822Name,
|
|
NameType: EmailNameType,
|
|
Name: "bücher@例.jp" + string(byte(0)),
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-idna-internationalized-domain-ascii",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@例.jp"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@xn---bla.jp"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotParseDomain,
|
|
NameType: EmailNameType,
|
|
Name: "mail@xn---bla.jp",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/uri-permitted-domain-wildcard",
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "example.com",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: URINameType,
|
|
Name: "https://example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/uri-permitted",
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("test.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "bad.local",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: URINameType,
|
|
Name: "https://bad.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/uri-permitted-with-literal-wildcard", // don't allow literal wildcard in URI, e.g. xxxx://*.domain.tld
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "*.local",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotMatchNameToConstraint,
|
|
NameType: URINameType,
|
|
Name: "https://*.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/uri-permitted-idna-internationalized-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("*.bücher.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "abc.bücher.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotMatchNameToConstraint,
|
|
NameType: URINameType,
|
|
Name: "https://abc.b%C3%BCcher.example.com",
|
|
},
|
|
},
|
|
// SINGLE SAN TYPE EXCLUDED FAILURE TESTS
|
|
{
|
|
name: "fail/dns-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedDNSDomains("*.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"www.example.com"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "www.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-excluded-single-host",
|
|
options: []NamePolicyOption{
|
|
WithExcludedDNSDomains("host.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"host.example.com"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "host.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/ipv4-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: IPNameType,
|
|
Name: "127.0.0.1",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/ipv6-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
|
Mask: net.CIDRMask(120, 128),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: IPNameType,
|
|
Name: "2001:db8:85a3::8a2e:370:7334",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@example.com"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "mail@example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/uri-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedURIDomains("*.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: URINameType,
|
|
Name: "https://www.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/uri-excluded-with-literal-wildcard", // don't allow literal wildcard in URI, e.g. xxxx://*.domain.tld
|
|
options: []NamePolicyOption{
|
|
WithExcludedURIDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "*.local",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotMatchNameToConstraint,
|
|
NameType: URINameType,
|
|
Name: "https://*.local",
|
|
},
|
|
},
|
|
// SUBJECT FAILURE TESTS
|
|
{
|
|
name: "fail/subject-permitted-no-match",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedCommonNames("this name is allowed", "and this one too"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "some certificate name",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed, // only permitted names allowed
|
|
NameType: CNNameType,
|
|
Name: "some certificate name",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-excluded-match",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedCommonNames("this name is not allowed"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "this name is not allowed",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotParseDomain, // CN cannot be parsed as DNS in this case
|
|
NameType: CNNameType,
|
|
Name: "this name is not allowed",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-dns-no-domain",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "name with space.local",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: CannotParseDomain,
|
|
NameType: CNNameType,
|
|
Name: "name with space.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-dns-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "example.notlocal",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "example.notlocal",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-dns-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "example.local",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "example.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-ipv4-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "10.10.10.10",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "10.10.10.10",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-ipv4-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "127.0.0.30",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "127.0.0.30",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-ipv6-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
|
Mask: net.CIDRMask(120, 128),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "2002:0db8:85a3:0000:0000:8a2e:0370:7339",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "2002:db8:85a3::8a2e:370:7339",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-ipv6-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
|
Mask: net.CIDRMask(120, 128),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "2001:0db8:85a3:0000:0000:8a2e:0370:7339",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "2001:db8:85a3::8a2e:370:7339",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-email-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedEmailAddresses("@example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "mail@smallstep.com",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "mail@smallstep.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-email-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedEmailAddresses("@example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "mail@example.local",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "mail@example.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-uri-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedURIDomains("*.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "https://www.google.com",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "https://www.google.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/subject-uri-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedURIDomains("*.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "https://www.example.com",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "https://www.example.com",
|
|
},
|
|
},
|
|
// DIFFERENT SAN PERMITTED FAILURE TESTS
|
|
{
|
|
name: "fail/dns-permitted-with-ip-name", // when only DNS is permitted, IPs are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: IPNameType,
|
|
Name: "127.0.0.1",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-with-mail", // when only DNS is permitted, mails are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@smallstep.com"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "mail@smallstep.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/dns-permitted-with-uri", // when only DNS is permitted, URIs are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: URINameType,
|
|
Name: "https://www.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/ip-permitted-with-dns-name", // when only IP is permitted, DNS names are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"www.example.com"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "www.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/ip-permitted-with-mail", // when only IP is permitted, mails are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@smallstep.com"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "mail@smallstep.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/ip-permitted-with-uri", // when only IP is permitted, URIs are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: URINameType,
|
|
Name: "https://www.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-with-dns-name", // when only mail is permitted, DNS names are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"www.example.com"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "www.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-with-ip", // when only mail is permitted, IPs are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{
|
|
net.ParseIP("127.0.0.1"),
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: IPNameType,
|
|
Name: "127.0.0.1",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/mail-permitted-with-uri", // when only mail is permitted, URIs are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: URINameType,
|
|
Name: "https://www.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/uri-permitted-with-dns-name", // when only URI is permitted, DNS names are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"host.local"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "host.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/uri-permitted-with-ip-name", // when only URI is permitted, IPs are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{
|
|
net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: IPNameType,
|
|
Name: "2001:db8:85a3::8a2e:370:7334",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/uri-permitted-with-ip-name", // when only URI is permitted, mails are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@smallstep.com"},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "mail@smallstep.com",
|
|
},
|
|
},
|
|
// COMBINED FAILURE TESTS
|
|
{
|
|
name: "fail/combined-simple-all-badhost.local-common-name",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedDNSDomains("*.local"),
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
WithPermittedEmailAddresses("@example.local"),
|
|
WithPermittedURIDomains("*.example.local"),
|
|
WithExcludedDNSDomains("badhost.local"),
|
|
WithExcludedCIDRs("127.0.0.128/25"),
|
|
WithExcludedEmailAddresses("badmail@example.local"),
|
|
WithExcludedURIDomains("badwww.example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "badhost.local",
|
|
},
|
|
DNSNames: []string{"example.local"},
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.40")},
|
|
EmailAddresses: []string{"mail@example.local"},
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.local",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: CNNameType,
|
|
Name: "badhost.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/combined-simple-all-anotherbadhost.local-dns",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
WithPermittedEmailAddresses("@example.local"),
|
|
WithPermittedURIDomains("*.example.local"),
|
|
WithExcludedDNSDomains("anotherbadhost.local"),
|
|
WithExcludedCIDRs("127.0.0.128/25"),
|
|
WithExcludedEmailAddresses("badmail@example.local"),
|
|
WithExcludedURIDomains("badwww.example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "badhost.local",
|
|
},
|
|
DNSNames: []string{"anotherbadhost.local"},
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.40")},
|
|
EmailAddresses: []string{"mail@example.local"},
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.local",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "anotherbadhost.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/combined-simple-all-badmail@example.local",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
WithPermittedEmailAddresses("@example.local"),
|
|
WithPermittedURIDomains("*.example.local"),
|
|
WithExcludedDNSDomains("badhost.local"),
|
|
WithExcludedCIDRs("127.0.0.128/25"),
|
|
WithExcludedEmailAddresses("badmail@example.local"),
|
|
WithExcludedURIDomains("badwww.example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "badhost.local",
|
|
},
|
|
DNSNames: []string{"example.local"},
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.40")},
|
|
EmailAddresses: []string{"mail@example.local", "badmail@example.local"},
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.local",
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "badmail@example.local",
|
|
},
|
|
},
|
|
// NO CONSTRAINT SUCCESS TESTS
|
|
{
|
|
name: "ok/dns-no-constraints",
|
|
options: []NamePolicyOption{},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"www.example.com"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/ipv4-no-constraints",
|
|
options: []NamePolicyOption{},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{
|
|
net.ParseIP("127.0.0.1"),
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/ipv6-no-constraints",
|
|
options: []NamePolicyOption{},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{
|
|
net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-no-constraints",
|
|
options: []NamePolicyOption{},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@smallstep.com"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-no-constraints",
|
|
options: []NamePolicyOption{},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-no-constraints",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "www.example.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-empty-no-constraints",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-permitted-match",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedCommonNames("this name is allowed"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "this name is allowed",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-excluded-match",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedCommonNames("this name is not allowed"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "some other name",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
// SINGLE SAN TYPE PERMITTED SUCCESS TESTS
|
|
{
|
|
name: "ok/dns-permitted",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"example.local"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/dns-permitted-wildcard",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local", "*.x509local"),
|
|
WithAllowLiteralWildcardNames(),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{
|
|
"host.local",
|
|
"test.x509local",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/dns-permitted-wildcard-literal",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local", "*.x509local"),
|
|
WithAllowLiteralWildcardNames(),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{
|
|
"*.local",
|
|
"*.x509local",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/dns-permitted-combined",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local", "*.x509local", "host.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{
|
|
"example.local",
|
|
"example.x509local",
|
|
"host.example.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/dns-permitted-idna-internationalized-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.例.jp"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{
|
|
"JP納豆.例.jp", // Example value from https://www.w3.org/International/articles/idn-and-iri/
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/ipv4-permitted",
|
|
options: []NamePolicyOption{
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.20")},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/ipv6-permitted",
|
|
options: []NamePolicyOption{
|
|
WithPermittedCIDRs("2001:0db8:85a3:0000:0000:8a2e:0370:7334/120"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7339")},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-permitted-wildcard",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{
|
|
"test@example.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-permitted-plain-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{
|
|
"test@example.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-permitted-specific-mailbox",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("test@local.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{
|
|
"test@local.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-permitted-idna-internationalized-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@例.jp"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-permitted-domain-wildcard",
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "example.local",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-permitted-specific-uri",
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("test.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "test.local",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-permitted-with-port",
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("*.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com:8080",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-permitted-idna-internationalized-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("*.bücher.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "abc.xn--bcher-kva.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-permitted-idna-internationalized-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedURIDomains("bücher.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "xn--bcher-kva.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
// SINGLE SAN TYPE EXCLUDED SUCCESS TESTS
|
|
{
|
|
name: "ok/dns-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedDNSDomains("*.notlocal"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"example.local"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/ipv4-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("10.10.10.10")},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/ipv6-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedCIDRs("2001:0db8:85a3:0000:0000:8a2e:0370:7334/120"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("2003:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedEmailAddresses("@notlocal"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@local"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-excluded-with-subdomain",
|
|
options: []NamePolicyOption{
|
|
WithExcludedEmailAddresses("@local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@example.local"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-excluded",
|
|
options: []NamePolicyOption{
|
|
WithExcludedURIDomains("*.google.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
// SUBJECT SUCCESS TESTS
|
|
{
|
|
name: "ok/subject-empty",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "",
|
|
},
|
|
DNSNames: []string{"example.local"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-dns-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "example.local",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-dns-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedDNSDomains("*.notlocal"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "example.local",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-ipv4-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "127.0.0.20",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-ipv4-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("128.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "127.0.0.1",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-ipv6-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
|
Mask: net.CIDRMask(120, 128),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "2001:0db8:85a3:0000:0000:8a2e:0370:7339",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-ipv6-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedIPRanges(
|
|
&net.IPNet{
|
|
IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
|
Mask: net.CIDRMask(120, 128),
|
|
},
|
|
),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "2009:0db8:85a3:0000:0000:8a2e:0370:7339",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-email-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedEmailAddresses("@example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "mail@example.local",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-email-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedEmailAddresses("@example.notlocal"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "mail@example.local",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-uri-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedURIDomains("*.example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "https://www.example.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/subject-uri-excluded",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedURIDomains("*.smallstep.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "https://www.example.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
// DIFFERENT SAN TYPE EXCLUDED SUCCESS TESTS
|
|
{
|
|
name: "ok/dns-excluded-with-ip-name", // when only DNS is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/dns-excluded-with-mail", // when only DNS is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@example.com"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/dns-excluded-with-mail", // when only DNS is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/ip-excluded-with-dns", // when only IP is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedCIDRs("127.0.0.1/24"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"test.local"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/ip-excluded-with-mail", // when only IP is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedCIDRs("127.0.0.1/24"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@example.com"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/ip-excluded-with-mail", // when only IP is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedCIDRs("127.0.0.1/24"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-excluded-with-dns", // when only mail is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"test.local"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-excluded-with-ip", // when only mail is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/mail-excluded-with-uri", // when only mail is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.com",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-excluded-with-dns", // when only URI is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedURIDomains("*.example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
DNSNames: []string{"test.example.local"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-excluded-with-dns", // when only URI is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedURIDomains("*.example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/uri-excluded-with-mail", // when only URI is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithExcludedURIDomains("*.example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
EmailAddresses: []string{"mail@example.local"},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/dns-excluded-with-subject-ip-name", // when only DNS is exluded, we allow anything else
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithExcludedDNSDomains("*.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "127.0.0.1",
|
|
},
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
},
|
|
want: true,
|
|
},
|
|
// COMBINED SUCCESS TESTS
|
|
{
|
|
name: "ok/combined-simple-permitted",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedDNSDomains("*.local"),
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
WithPermittedEmailAddresses("@example.local"),
|
|
WithPermittedURIDomains("*.example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "somehost.local",
|
|
},
|
|
DNSNames: []string{"example.local"},
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.15")},
|
|
EmailAddresses: []string{"mail@example.local"},
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.local",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/combined-simple-permitted-without-subject-verification",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
WithPermittedEmailAddresses("@example.local"),
|
|
WithPermittedURIDomains("*.example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "forbidden-but-non-verified-domain.example.com",
|
|
},
|
|
DNSNames: []string{"example.local"},
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
EmailAddresses: []string{"mail@example.local"},
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.local",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/combined-simple-all",
|
|
options: []NamePolicyOption{
|
|
WithSubjectCommonNameVerification(),
|
|
WithPermittedDNSDomains("*.local"),
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
WithPermittedEmailAddresses("@example.local"),
|
|
WithPermittedURIDomains("*.example.local"),
|
|
WithExcludedDNSDomains("badhost.local"),
|
|
WithExcludedCIDRs("127.0.0.128/25"),
|
|
WithExcludedEmailAddresses("badmail@example.local"),
|
|
WithExcludedURIDomains("badwww.example.local"),
|
|
},
|
|
cert: &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "somehost.local",
|
|
},
|
|
DNSNames: []string{"example.local"},
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
EmailAddresses: []string{"mail@example.local"},
|
|
URIs: []*url.URL{
|
|
{
|
|
Scheme: "https",
|
|
Host: "www.example.local",
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
engine, err := New(tt.options...)
|
|
assert.NoError(t, err)
|
|
gotErr := engine.IsX509CertificateAllowed(tt.cert)
|
|
wantErr := tt.wantErr != nil
|
|
|
|
if (gotErr != nil) != wantErr {
|
|
t.Errorf("NamePolicyEngine.IsX509CertificateAllowed() error = %v, wantErr %v", gotErr, tt.wantErr)
|
|
return
|
|
}
|
|
if gotErr != nil {
|
|
var npe *NamePolicyError
|
|
assert.True(t, errors.As(gotErr, &npe))
|
|
assert.NotEqual(t, "", npe.Error())
|
|
assert.Equal(t, tt.wantErr.Reason, npe.Reason)
|
|
assert.Equal(t, tt.wantErr.NameType, npe.NameType)
|
|
assert.Equal(t, tt.wantErr.Name, npe.Name)
|
|
assert.NotEqual(t, "", npe.Detail())
|
|
//assert.Equals(t, tt.err.Reason, npe.Reason) // NOTE: reason detail is skipped; it's a detail
|
|
}
|
|
|
|
// Perform the same tests for a CSR, which are similar to Certificates
|
|
csr := &x509.CertificateRequest{
|
|
Subject: tt.cert.Subject,
|
|
DNSNames: tt.cert.DNSNames,
|
|
EmailAddresses: tt.cert.EmailAddresses,
|
|
IPAddresses: tt.cert.IPAddresses,
|
|
URIs: tt.cert.URIs,
|
|
}
|
|
gotErr = engine.IsX509CertificateRequestAllowed(csr)
|
|
wantErr = tt.wantErr != nil
|
|
if (gotErr != nil) != wantErr {
|
|
t.Errorf("NamePolicyEngine.AreCSRNamesAllowed() error = %v, wantErr %v", gotErr, tt.wantErr)
|
|
return
|
|
}
|
|
if gotErr != nil {
|
|
var npe *NamePolicyError
|
|
assert.True(t, errors.As(gotErr, &npe))
|
|
assert.NotEqual(t, "", npe.Error())
|
|
assert.Equal(t, tt.wantErr.Reason, npe.Reason)
|
|
assert.Equal(t, tt.wantErr.NameType, npe.NameType)
|
|
assert.Equal(t, tt.wantErr.Name, npe.Name)
|
|
assert.NotEqual(t, "", npe.Detail())
|
|
//assert.Equals(t, tt.err.Reason, npe.Reason) // NOTE: reason detail is skipped; it's a detail
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNamePolicyEngine_SSH_ArePrincipalsAllowed(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
options []NamePolicyOption
|
|
cert *ssh.Certificate
|
|
want bool
|
|
wantErr *NamePolicyError
|
|
}{
|
|
{
|
|
name: "fail/host-with-permitted-dns-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"host.example.com",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "host.example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/host-with-excluded-dns-domain",
|
|
options: []NamePolicyOption{
|
|
WithExcludedDNSDomains("*.local"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"host.local",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "host.local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/host-with-permitted-cidr",
|
|
options: []NamePolicyOption{
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"192.168.0.22",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: IPNameType,
|
|
Name: "192.168.0.22",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/host-with-excluded-cidr",
|
|
options: []NamePolicyOption{
|
|
WithExcludedCIDRs("127.0.0.1/24"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"127.0.0.0",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: IPNameType,
|
|
Name: "127.0.0.0",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/user-with-permitted-email",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"mail@local",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "mail@local",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/user-with-excluded-email",
|
|
options: []NamePolicyOption{
|
|
WithExcludedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"mail@example.com",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "mail@example.com",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/host-with-permitted-principals",
|
|
options: []NamePolicyOption{
|
|
WithPermittedPrincipals("localhost"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"host",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "host",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/user-with-permitted-principals",
|
|
options: []NamePolicyOption{
|
|
WithPermittedPrincipals("user"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"root",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: PrincipalNameType,
|
|
Name: "root",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/user-with-excluded-principals",
|
|
options: []NamePolicyOption{
|
|
WithExcludedPrincipals("user"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"user",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: PrincipalNameType,
|
|
Name: "user",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/user-with-permitted-principal-as-mail",
|
|
options: []NamePolicyOption{
|
|
WithPermittedPrincipals("ops"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"ops@work", // this is (currently) parsed as an email-like principal; not allowed with just "ops" as the permitted principal
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "ops@work",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/host-principal-with-permitted-dns-domain", // when only DNS is permitted, username principals are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"user",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "user",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/host-principal-with-permitted-ip-range", // when only IPs are permitted, username principals are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"user",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "user",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/user-principal-with-permitted-email", // when only emails are permitted, username principals are not allowed.
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"user",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: PrincipalNameType,
|
|
Name: "user",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/combined-user",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@smallstep.com"),
|
|
WithExcludedEmailAddresses("root@smallstep.com"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"someone@smallstep.com",
|
|
"someone",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: PrincipalNameType,
|
|
Name: "someone",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/combined-user-with-excluded-user-principal",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@smallstep.com"),
|
|
WithExcludedPrincipals("root"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"someone@smallstep.com",
|
|
"root",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: PrincipalNameType,
|
|
Name: "root",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/host-with-permitted-user-principals",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@work"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"example.work",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "example.work",
|
|
},
|
|
},
|
|
{
|
|
name: "fail/user-with-permitted-user-principals",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"herman@work",
|
|
},
|
|
},
|
|
want: false,
|
|
wantErr: &NamePolicyError{
|
|
Reason: NotAllowed,
|
|
NameType: EmailNameType,
|
|
Name: "herman@work",
|
|
},
|
|
},
|
|
{
|
|
name: "ok/host-with-permitted-dns-domain",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"host.local",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/host-with-excluded-dns-domain",
|
|
options: []NamePolicyOption{
|
|
WithExcludedDNSDomains("*.example.com"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"host.local",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/host-with-permitted-ip",
|
|
options: []NamePolicyOption{
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"127.0.0.33",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/host-with-excluded-ip",
|
|
options: []NamePolicyOption{
|
|
WithExcludedCIDRs("127.0.0.1/24"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"192.168.0.35",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/host-with-excluded-principals",
|
|
options: []NamePolicyOption{
|
|
WithExcludedPrincipals("localhost"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"localhost",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/user-with-permitted-email",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"mail@example.com",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/user-with-excluded-email",
|
|
options: []NamePolicyOption{
|
|
WithExcludedEmailAddresses("@example.com"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"mail@local",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/user-with-permitted-principals",
|
|
options: []NamePolicyOption{
|
|
WithPermittedPrincipals("*"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"user",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/user-with-excluded-principals",
|
|
options: []NamePolicyOption{
|
|
WithExcludedPrincipals("user"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"root",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/combined-user",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@smallstep.com"),
|
|
WithPermittedPrincipals("*"), // without specifying the wildcard, "someone" would not be allowed.
|
|
WithExcludedEmailAddresses("root@smallstep.com"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"someone@smallstep.com",
|
|
"someone",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/combined-user-with-excluded-user-principal",
|
|
options: []NamePolicyOption{
|
|
WithPermittedEmailAddresses("@smallstep.com"),
|
|
WithExcludedEmailAddresses("root@smallstep.com"),
|
|
WithExcludedPrincipals("root"), // unlike the previous test, this implicitly allows any other username principal
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{
|
|
"someone@smallstep.com",
|
|
"someone",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ok/combined-host",
|
|
options: []NamePolicyOption{
|
|
WithPermittedDNSDomains("*.local"),
|
|
WithPermittedCIDRs("127.0.0.1/24"),
|
|
WithExcludedDNSDomains("badhost.local"),
|
|
WithExcludedCIDRs("127.0.0.128/25"),
|
|
},
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{
|
|
"example.local",
|
|
"127.0.0.31",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
engine, err := New(tt.options...)
|
|
assert.NoError(t, err)
|
|
gotErr := engine.IsSSHCertificateAllowed(tt.cert)
|
|
wantErr := tt.wantErr != nil
|
|
if (gotErr != nil) != wantErr {
|
|
t.Errorf("NamePolicyEngine.IsSSHCertificateAllowed() error = %v, wantErr %v", gotErr, tt.wantErr)
|
|
return
|
|
}
|
|
if gotErr != nil {
|
|
var npe *NamePolicyError
|
|
assert.True(t, errors.As(gotErr, &npe))
|
|
assert.NotEqual(t, "", npe.Error())
|
|
assert.Equal(t, tt.wantErr.Reason, npe.Reason)
|
|
assert.Equal(t, tt.wantErr.NameType, npe.NameType)
|
|
assert.Equal(t, tt.wantErr.Name, npe.Name)
|
|
assert.NotEqual(t, "", npe.Detail())
|
|
//assert.Equals(t, tt.err.Reason, npe.Reason) // NOTE: reason detail is skipped; it's a detail
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type result struct {
|
|
wantDNSNames []string
|
|
wantIps []net.IP
|
|
wantEmails []string
|
|
wantUsernames []string
|
|
}
|
|
|
|
func emptyResult() result {
|
|
return result{
|
|
wantDNSNames: []string{},
|
|
wantIps: []net.IP{},
|
|
wantEmails: []string{},
|
|
wantUsernames: []string{},
|
|
}
|
|
}
|
|
|
|
func Test_splitSSHPrincipals(t *testing.T) {
|
|
type test struct {
|
|
cert *ssh.Certificate
|
|
r result
|
|
wantErr bool
|
|
}
|
|
var tests = map[string]func(t *testing.T) test{
|
|
"fail/unexpected-cert-type": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: uint32(0),
|
|
},
|
|
r: r,
|
|
wantErr: true,
|
|
}
|
|
},
|
|
"fail/user-ip": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
r.wantIps = []net.IP{net.ParseIP("127.0.0.1")}
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{"127.0.0.1"},
|
|
},
|
|
r: r,
|
|
wantErr: true,
|
|
}
|
|
},
|
|
"fail/user-uri": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{"https://host.local/"},
|
|
},
|
|
r: r,
|
|
wantErr: true,
|
|
}
|
|
},
|
|
"fail/host-uri": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{"https://host.local/"},
|
|
},
|
|
r: r,
|
|
wantErr: true,
|
|
}
|
|
},
|
|
"ok/host-dns": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
r.wantDNSNames = []string{"host.example.com"}
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{"host.example.com"},
|
|
},
|
|
r: r,
|
|
wantErr: false,
|
|
}
|
|
},
|
|
"ok/host-ip": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
r.wantIps = []net.IP{net.ParseIP("127.0.0.1")}
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{"127.0.0.1"},
|
|
},
|
|
r: r,
|
|
wantErr: false,
|
|
}
|
|
},
|
|
"ok/host-email": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
r.wantEmails = []string{"ops@work"}
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.HostCert,
|
|
ValidPrincipals: []string{"ops@work"},
|
|
},
|
|
r: r,
|
|
wantErr: false,
|
|
}
|
|
},
|
|
"ok/user-localhost": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
r.wantUsernames = []string{"localhost"} // when type is User cert, this is considered a username; not a DNS
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{"localhost"},
|
|
},
|
|
r: r,
|
|
wantErr: false,
|
|
}
|
|
},
|
|
"ok/user-username-with-period": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
r.wantUsernames = []string{"x.joe"}
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{"x.joe"},
|
|
},
|
|
r: r,
|
|
wantErr: false,
|
|
}
|
|
},
|
|
"ok/user-maillike": func(t *testing.T) test {
|
|
r := emptyResult()
|
|
r.wantEmails = []string{"ops@work"}
|
|
return test{
|
|
cert: &ssh.Certificate{
|
|
CertType: ssh.UserCert,
|
|
ValidPrincipals: []string{"ops@work"},
|
|
},
|
|
r: r,
|
|
wantErr: false,
|
|
}
|
|
},
|
|
}
|
|
for name, prep := range tests {
|
|
tt := prep(t)
|
|
t.Run(name, func(t *testing.T) {
|
|
gotDNSNames, gotIps, gotEmails, gotUsernames, err := splitSSHPrincipals(tt.cert)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("splitSSHPrincipals() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !cmp.Equal(tt.r.wantDNSNames, gotDNSNames) {
|
|
t.Errorf("splitSSHPrincipals() DNS names diff =\n%s", cmp.Diff(tt.r.wantDNSNames, gotDNSNames))
|
|
}
|
|
if !cmp.Equal(tt.r.wantIps, gotIps) {
|
|
t.Errorf("splitSSHPrincipals() IPs diff =\n%s", cmp.Diff(tt.r.wantIps, gotIps))
|
|
}
|
|
if !cmp.Equal(tt.r.wantEmails, gotEmails) {
|
|
t.Errorf("splitSSHPrincipals() Emails diff =\n%s", cmp.Diff(tt.r.wantEmails, gotEmails))
|
|
}
|
|
if !cmp.Equal(tt.r.wantUsernames, gotUsernames) {
|
|
t.Errorf("splitSSHPrincipals() Usernames diff =\n%s", cmp.Diff(tt.r.wantUsernames, gotUsernames))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_removeDuplicates(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "empty-slice",
|
|
input: []string{},
|
|
want: []string{},
|
|
},
|
|
{
|
|
name: "single-item",
|
|
input: []string{"x"},
|
|
want: []string{"x"},
|
|
},
|
|
{
|
|
name: "ok",
|
|
input: []string{"x", "y", "x", "z", "x", "z", "y"},
|
|
want: []string{"x", "y", "z"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := removeDuplicates(tt.input); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("removeDuplicates() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_removeDuplicateIPNets(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []*net.IPNet
|
|
want []*net.IPNet
|
|
}{
|
|
{
|
|
name: "empty-slice",
|
|
input: []*net.IPNet{},
|
|
want: []*net.IPNet{},
|
|
},
|
|
{
|
|
name: "single-item",
|
|
input: []*net.IPNet{
|
|
{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 255),
|
|
},
|
|
},
|
|
want: []*net.IPNet{
|
|
{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 255),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "multiple",
|
|
input: []*net.IPNet{
|
|
{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 255),
|
|
},
|
|
{
|
|
IP: net.ParseIP("192.168.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 255),
|
|
},
|
|
{
|
|
IP: net.ParseIP("10.10.0.0"),
|
|
Mask: net.IPv4Mask(255, 255, 0, 0),
|
|
},
|
|
{
|
|
IP: net.ParseIP("192.168.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 255),
|
|
},
|
|
{
|
|
IP: net.ParseIP("192.168.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
},
|
|
want: []*net.IPNet{
|
|
{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 255),
|
|
},
|
|
{
|
|
IP: net.ParseIP("192.168.0.1"),
|
|
Mask: net.IPv4Mask(255, 255, 255, 0),
|
|
},
|
|
{
|
|
IP: net.ParseIP("10.10.0.0"),
|
|
Mask: net.IPv4Mask(255, 255, 0, 0),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if gotRet := removeDuplicateIPNets(tt.input); !reflect.DeepEqual(gotRet, tt.want) {
|
|
t.Errorf("removeDuplicateIPNets() = %v, want %v", gotRet, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNamePolicyError_Error(t *testing.T) {
|
|
type fields struct {
|
|
Reason NamePolicyReason
|
|
NameType NameType
|
|
Name string
|
|
detail string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
want string
|
|
}{
|
|
{
|
|
name: "dns-not-allowed",
|
|
fields: fields{
|
|
Reason: NotAllowed,
|
|
NameType: DNSNameType,
|
|
Name: "www.example.com",
|
|
},
|
|
want: "dns name \"www.example.com\" not allowed",
|
|
},
|
|
{
|
|
name: "dns-cannot-parse-domain",
|
|
fields: fields{
|
|
Reason: CannotParseDomain,
|
|
NameType: DNSNameType,
|
|
Name: "www.example.com",
|
|
},
|
|
want: "cannot parse dns domain \"www.example.com\"",
|
|
},
|
|
{
|
|
name: "email-cannot-parse",
|
|
fields: fields{
|
|
Reason: CannotParseRFC822Name,
|
|
NameType: EmailNameType,
|
|
Name: "mail@example.com",
|
|
},
|
|
want: "cannot parse email rfc822Name \"mail@example.com\"",
|
|
},
|
|
{
|
|
name: "uri-cannot-match",
|
|
fields: fields{
|
|
Reason: CannotMatchNameToConstraint,
|
|
NameType: URINameType,
|
|
Name: "https://*.local",
|
|
},
|
|
want: "error matching uri name \"https://*.local\" to constraint",
|
|
},
|
|
{
|
|
name: "unknown",
|
|
fields: fields{
|
|
Reason: -1,
|
|
NameType: DNSNameType,
|
|
Name: "some name",
|
|
detail: "detail string",
|
|
},
|
|
want: "unknown error reason (-1): detail string",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
e := &NamePolicyError{
|
|
Reason: tt.fields.Reason,
|
|
NameType: tt.fields.NameType,
|
|
Name: tt.fields.Name,
|
|
detail: tt.fields.detail,
|
|
}
|
|
if got := e.Error(); got != tt.want {
|
|
t.Errorf("NamePolicyError.Error() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|