@marcel91200 / caddy Mirror · read-only / commits / 931656bd
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 12, 17:00 UTC

acmeserver: add policy field to define allow/deny rules (#5796)

* acmeserver: support specifying the allowed challenge types * add caddyfile adapt tests * acmeserver: add `policy` field to define allow/deny rules * allow `omitempty` to work * add caddyfile support for `policy` * remove "uri domain" policy * fmt the files * add docs * do not support `CommonName`; the field is deprecated * r/DNSDomains/Domains/g * Caddyfile docs * add tests * move `Policy` to top of file

Mohammed Al Sahaf committed Feb 24, 2024 at 02:26 UTC 931656bd6868a87eef4814cc02b5125a907662bf
6 files changed +506 -66
caddytest/integration/acme_test.go
+8 -63
@@ -5,13 +5,9 @@ import (
5 "crypto/ecdsa"
6 "crypto/elliptic"
7 "crypto/rand"
8 - "crypto/tls"
9 - "crypto/x509"
8 "fmt"
9 "net"
10 "net/http"
13 - "os"
14 - "path/filepath"
11 "strings"
12 "testing"
13
@@ -48,37 +44,11 @@ func TestACMEServerWithDefaults(t *testing.T) {
44 }
45 `, "caddyfile")
46
51 - datadir := caddy.AppDataDir()
52 - rootCertsGlob := filepath.Join(datadir, "pki", "authorities", "local", "*.crt")
53 - matches, err := filepath.Glob(rootCertsGlob)
54 - if err != nil {
55 - t.Errorf("could not find root certs: %s", err)
56 - return
57 - }
58 - certPool := x509.NewCertPool()
59 - for _, m := range matches {
60 - certPem, err := os.ReadFile(m)
61 - if err != nil {
62 - t.Errorf("reading cert file '%s' error: %s", m, err)
63 - return
64 - }
65 - if !certPool.AppendCertsFromPEM(certPem) {
66 - t.Errorf("failed to append the cert: %s", m)
67 - return
68 - }
69 - }
70 -
47 client := acmez.Client{
48 Client: &acme.Client{
73 - Directory: "https://acme.localhost:9443/acme/local/directory",
74 - HTTPClient: &http.Client{
75 - Transport: &http.Transport{
76 - TLSClientConfig: &tls.Config{
77 - RootCAs: certPool,
78 - },
79 - },
80 - },
81 - Logger: logger,
49 + Directory: "https://acme.localhost:9443/acme/local/directory",
50 + HTTPClient: tester.Client,
51 + Logger: logger,
52 },
53 ChallengeSolvers: map[string]acmez.Solver{
54 acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@@ -143,37 +113,11 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
113 }
114 `, "caddyfile")
115
146 - datadir := caddy.AppDataDir()
147 - rootCertsGlob := filepath.Join(datadir, "pki", "authorities", "local", "*.crt")
148 - matches, err := filepath.Glob(rootCertsGlob)
149 - if err != nil {
150 - t.Errorf("could not find root certs: %s", err)
151 - return
152 - }
153 - certPool := x509.NewCertPool()
154 - for _, m := range matches {
155 - certPem, err := os.ReadFile(m)
156 - if err != nil {
157 - t.Errorf("reading cert file '%s' error: %s", m, err)
158 - return
159 - }
160 - if !certPool.AppendCertsFromPEM(certPem) {
161 - t.Errorf("failed to append the cert: %s", m)
162 - return
163 - }
164 - }
165 -
116 client := acmez.Client{
117 Client: &acme.Client{
168 - Directory: "https://acme.localhost:9443/acme/local/directory",
169 - HTTPClient: &http.Client{
170 - Transport: &http.Transport{
171 - TLSClientConfig: &tls.Config{
172 - RootCAs: certPool,
173 - },
174 - },
175 - },
176 - Logger: logger,
118 + Directory: "https://acme.localhost:9443/acme/local/directory",
119 + HTTPClient: tester.Client,
120 + Logger: logger,
121 },
122 ChallengeSolvers: map[string]acmez.Solver{
123 acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
@@ -224,12 +168,13 @@ type naiveHTTPSolver struct {
168 func (s *naiveHTTPSolver) Present(ctx context.Context, challenge acme.Challenge) error {
169 smallstepacme.InsecurePortHTTP01 = acmeChallengePort
170 s.srv = &http.Server{
227 - Addr: fmt.Sprintf("localhost:%d", acmeChallengePort),
171 + Addr: fmt.Sprintf(":%d", acmeChallengePort),
172 Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
173 host, _, err := net.SplitHostPort(r.Host)
174 if err != nil {
175 host = r.Host
176 }
177 + s.logger.Info("received request on challenge server", zap.String("path", r.URL.Path))
178 if r.Method == "GET" && r.URL.Path == challenge.HTTP01ResourcePath() && strings.EqualFold(host, challenge.Identifier.Value) {
179 w.Header().Add("Content-Type", "text/plain")
180 w.Write([]byte(challenge.KeyAuthorization))
caddytest/integration/acmeserver_test.go
+176
@@ -1,9 +1,17 @@
1 package integration
2
3 import (
4 + "context"
5 + "crypto/ecdsa"
6 + "crypto/elliptic"
7 + "crypto/rand"
8 + "strings"
9 "testing"
10
11 "github.com/caddyserver/caddy/v2/caddytest"
12 + "github.com/mholt/acmez"
13 + "github.com/mholt/acmez/acme"
14 + "go.uber.org/zap"
15 )
16
17 func TestACMEServerDirectory(t *testing.T) {
@@ -31,3 +39,171 @@ func TestACMEServerDirectory(t *testing.T) {
39 `{"newNonce":"https://acme.localhost:9443/acme/local/new-nonce","newAccount":"https://acme.localhost:9443/acme/local/new-account","newOrder":"https://acme.localhost:9443/acme/local/new-order","revokeCert":"https://acme.localhost:9443/acme/local/revoke-cert","keyChange":"https://acme.localhost:9443/acme/local/key-change"}
40 `)
41 }
42 +
43 +func TestACMEServerAllowPolicy(t *testing.T) {
44 + tester := caddytest.NewTester(t)
45 + tester.InitServer(`
46 + {
47 + skip_install_trust
48 + local_certs
49 + admin localhost:2999
50 + http_port 9080
51 + https_port 9443
52 + pki {
53 + ca local {
54 + name "Caddy Local Authority"
55 + }
56 + }
57 + }
58 + acme.localhost {
59 + acme_server {
60 + challenges http-01
61 + allow {
62 + domains localhost
63 + }
64 + }
65 + }
66 + `, "caddyfile")
67 +
68 + ctx := context.Background()
69 + logger, err := zap.NewDevelopment()
70 + if err != nil {
71 + t.Error(err)
72 + return
73 + }
74 +
75 + client := acmez.Client{
76 + Client: &acme.Client{
77 + Directory: "https://acme.localhost:9443/acme/local/directory",
78 + HTTPClient: tester.Client,
79 + Logger: logger,
80 + },
81 + ChallengeSolvers: map[string]acmez.Solver{
82 + acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
83 + },
84 + }
85 +
86 + accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
87 + if err != nil {
88 + t.Errorf("generating account key: %v", err)
89 + }
90 + account := acme.Account{
91 + Contact: []string{"mailto:you@example.com"},
92 + TermsOfServiceAgreed: true,
93 + PrivateKey: accountPrivateKey,
94 + }
95 + account, err = client.NewAccount(ctx, account)
96 + if err != nil {
97 + t.Errorf("new account: %v", err)
98 + return
99 + }
100 +
101 + // Every certificate needs a key.
102 + certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
103 + if err != nil {
104 + t.Errorf("generating certificate key: %v", err)
105 + return
106 + }
107 + {
108 + certs, err := client.ObtainCertificate(
109 + ctx,
110 + account,
111 + certPrivateKey,
112 + []string{"localhost"},
113 + )
114 + if err != nil {
115 + t.Errorf("obtaining certificate for allowed domain: %v", err)
116 + return
117 + }
118 +
119 + // ACME servers should usually give you the entire certificate chain
120 + // in PEM format, and sometimes even alternate chains! It's up to you
121 + // which one(s) to store and use, but whatever you do, be sure to
122 + // store the certificate and key somewhere safe and secure, i.e. don't
123 + // lose them!
124 + for _, cert := range certs {
125 + t.Logf("Certificate %q:\n%s\n\n", cert.URL, cert.ChainPEM)
126 + }
127 + }
128 + {
129 + _, err := client.ObtainCertificate(ctx, account, certPrivateKey, []string{"not-matching.localhost"})
130 + if err == nil {
131 + t.Errorf("obtaining certificate for 'not-matching.localhost' domain")
132 + } else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
133 + t.Logf("unexpected error: %v", err)
134 + }
135 + }
136 +}
137 +
138 +func TestACMEServerDenyPolicy(t *testing.T) {
139 + tester := caddytest.NewTester(t)
140 + tester.InitServer(`
141 + {
142 + skip_install_trust
143 + local_certs
144 + admin localhost:2999
145 + http_port 9080
146 + https_port 9443
147 + pki {
148 + ca local {
149 + name "Caddy Local Authority"
150 + }
151 + }
152 + }
153 + acme.localhost {
154 + acme_server {
155 + deny {
156 + domains deny.localhost
157 + }
158 + }
159 + }
160 + `, "caddyfile")
161 +
162 + ctx := context.Background()
163 + logger, err := zap.NewDevelopment()
164 + if err != nil {
165 + t.Error(err)
166 + return
167 + }
168 +
169 + client := acmez.Client{
170 + Client: &acme.Client{
171 + Directory: "https://acme.localhost:9443/acme/local/directory",
172 + HTTPClient: tester.Client,
173 + Logger: logger,
174 + },
175 + ChallengeSolvers: map[string]acmez.Solver{
176 + acme.ChallengeTypeHTTP01: &naiveHTTPSolver{logger: logger},
177 + },
178 + }
179 +
180 + accountPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
181 + if err != nil {
182 + t.Errorf("generating account key: %v", err)
183 + }
184 + account := acme.Account{
185 + Contact: []string{"mailto:you@example.com"},
186 + TermsOfServiceAgreed: true,
187 + PrivateKey: accountPrivateKey,
188 + }
189 + account, err = client.NewAccount(ctx, account)
190 + if err != nil {
191 + t.Errorf("new account: %v", err)
192 + return
193 + }
194 +
195 + // Every certificate needs a key.
196 + certPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
197 + if err != nil {
198 + t.Errorf("generating certificate key: %v", err)
199 + return
200 + }
201 + {
202 + _, err := client.ObtainCertificate(ctx, account, certPrivateKey, []string{"deny.localhost"})
203 + if err == nil {
204 + t.Errorf("obtaining certificate for 'deny.localhost' domain")
205 + } else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
206 + t.Logf("unexpected error: %v", err)
207 + }
208 + }
209 +}
modules/caddypki/acmeserver/acmeserver.go
+7 -1
@@ -96,6 +96,9 @@ type Handler struct {
96 // "http-01", "dns-01", "tls-alpn-01"
97 Challenges ACMEChallenges `json:"challenges,omitempty" `
98
99 + // The policy to use for issuing certificates
100 + Policy *Policy `json:"policy,omitempty"`
101 +
102 logger *zap.Logger
103 resolvers []caddy.NetworkAddress
104 ctx caddy.Context
@@ -165,7 +168,10 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
168 &provisioner.ACME{
169 Name: ash.CA,
170 Challenges: ash.Challenges.toSmallstepType(),
168 - Type: provisioner.TypeACME.String(),
171 + Options: &provisioner.Options{
172 + X509: ash.Policy.normalizeRules(),
173 + },
174 + Type: provisioner.TypeACME.String(),
175 Claims: &provisioner.Claims{
176 MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
177 MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour * 365},
modules/caddypki/acmeserver/caddyfile.go
+56 -2
@@ -33,6 +33,15 @@ func init() {
33 // lifetime <duration>
34 // resolvers <addresses...>
35 // challenges <challenges...>
36 +// allow_wildcard_names
37 +// allow {
38 +// domains <domains...>
39 +// ip_ranges <addresses...>
40 +// }
41 +// deny {
42 +// domains <domains...>
43 +// ip_ranges <addresses...>
44 +// }
45 // }
46 func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
47 h.Next() // consume directive name
@@ -60,7 +69,6 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
69 ca = new(caddypki.CA)
70 }
71 ca.ID = acmeServer.CA
63 -
72 case "lifetime":
73 if !h.NextArg() {
74 return nil, h.ArgErr()
@@ -70,7 +78,6 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
78 if err != nil {
79 return nil, err
80 }
73 -
81 if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d {
82 return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d)
83 }
@@ -82,6 +89,53 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
89 }
90 case "challenges":
91 acmeServer.Challenges = append(acmeServer.Challenges, stringToChallenges(h.RemainingArgs())...)
92 + case "allow_wildcard_names":
93 + if acmeServer.Policy == nil {
94 + acmeServer.Policy = &Policy{}
95 + }
96 + acmeServer.Policy.AllowWildcardNames = true
97 + case "allow":
98 + r := &RuleSet{}
99 + for h.Next() {
100 + for h.NextBlock(h.Nesting() - 1) {
101 + if h.CountRemainingArgs() == 0 {
102 + return nil, h.ArgErr() // TODO:
103 + }
104 + switch h.Val() {
105 + case "domains":
106 + r.Domains = append(r.Domains, h.RemainingArgs()...)
107 + case "ip_ranges":
108 + r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
109 + default:
110 + return nil, h.Errf("unrecognized 'allow' subdirective: %s", h.Val())
111 + }
112 + }
113 + }
114 + if acmeServer.Policy == nil {
115 + acmeServer.Policy = &Policy{}
116 + }
117 + acmeServer.Policy.Allow = r
118 + case "deny":
119 + r := &RuleSet{}
120 + for h.Next() {
121 + for h.NextBlock(h.Nesting() - 1) {
122 + if h.CountRemainingArgs() == 0 {
123 + return nil, h.ArgErr() // TODO:
124 + }
125 + switch h.Val() {
126 + case "domains":
127 + r.Domains = append(r.Domains, h.RemainingArgs()...)
128 + case "ip_ranges":
129 + r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
130 + default:
131 + return nil, h.Errf("unrecognized 'deny' subdirective: %s", h.Val())
132 + }
133 + }
134 + }
135 + if acmeServer.Policy == nil {
136 + acmeServer.Policy = &Policy{}
137 + }
138 + acmeServer.Policy.Deny = r
139 default:
140 return nil, h.Errf("unrecognized ACME server directive: %s", h.Val())
141 }
modules/caddypki/acmeserver/policy.go new
+83
@@ -0,0 +1,83 @@
1 +package acmeserver
2 +
3 +import (
4 + "github.com/smallstep/certificates/authority/policy"
5 + "github.com/smallstep/certificates/authority/provisioner"
6 +)
7 +
8 +// Policy defines the criteria for the ACME server
9 +// of when to issue a certificate. Refer to the
10 +// [Certificate Issuance Policy](https://smallstep.com/docs/step-ca/policies/)
11 +// on Smallstep website for the evaluation criteria.
12 +type Policy struct {
13 + // If a rule set is configured to allow a certain type of name,
14 + // all other types of names are automatically denied.
15 + Allow *RuleSet `json:"allow,omitempty"`
16 +
17 + // If a rule set is configured to deny a certain type of name,
18 + // all other types of names are still allowed.
19 + Deny *RuleSet `json:"deny,omitempty"`
20 +
21 + // If set to true, the ACME server will allow issuing wildcard certificates.
22 + AllowWildcardNames bool `json:"allow_wildcard_names,omitempty"`
23 +}
24 +
25 +// RuleSet is the specific set of SAN criteria for a certificate
26 +// to be issued or denied.
27 +type RuleSet struct {
28 + // Domains is a list of DNS domains that are allowed to be issued.
29 + // It can be in the form of FQDN for specific domain name, or
30 + // a wildcard domain name format, e.g. *.example.com, to allow
31 + // sub-domains of a domain.
32 + Domains []string `json:"domains,omitempty"`
33 +
34 + // IP ranges in the form of CIDR notation or specific IP addresses
35 + // to be approved or denied for certificates. Non-CIDR IP addresses
36 + // are matched exactly.
37 + IPRanges []string `json:"ip_ranges,omitempty"`
38 +}
39 +
40 +// normalizeAllowRules returns `nil` if policy is nil, the `Allow` rule is `nil`,
41 +// or all rules within the `Allow` rule are empty. Otherwise, it returns the X509NameOptions
42 +// with the content of the `Allow` rule.
43 +func (p *Policy) normalizeAllowRules() *policy.X509NameOptions {
44 + if (p == nil) || (p.Allow == nil) || (len(p.Allow.Domains) == 0 && len(p.Allow.IPRanges) == 0) {
45 + return nil
46 + }
47 + return &policy.X509NameOptions{
48 + DNSDomains: p.Allow.Domains,
49 + IPRanges: p.Allow.IPRanges,
50 + }
51 +}
52 +
53 +// normalizeDenyRules returns `nil` if policy is nil, the `Deny` rule is `nil`,
54 +// or all rules within the `Deny` rule are empty. Otherwise, it returns the X509NameOptions
55 +// with the content of the `Deny` rule.
56 +func (p *Policy) normalizeDenyRules() *policy.X509NameOptions {
57 + if (p == nil) || (p.Deny == nil) || (len(p.Deny.Domains) == 0 && len(p.Deny.IPRanges) == 0) {
58 + return nil
59 + }
60 + return &policy.X509NameOptions{
61 + DNSDomains: p.Deny.Domains,
62 + IPRanges: p.Deny.IPRanges,
63 + }
64 +}
65 +
66 +// normalizeRules returns `nil` if policy is nil, the `Allow` and `Deny` rules are `nil`,
67 +func (p *Policy) normalizeRules() *provisioner.X509Options {
68 + if p == nil {
69 + return nil
70 + }
71 +
72 + allow := p.normalizeAllowRules()
73 + deny := p.normalizeDenyRules()
74 + if allow == nil && deny == nil && !p.AllowWildcardNames {
75 + return nil
76 + }
77 +
78 + return &provisioner.X509Options{
79 + AllowedNames: allow,
80 + DeniedNames: deny,
81 + AllowWildcardNames: p.AllowWildcardNames,
82 + }
83 +}
modules/caddypki/acmeserver/policy_test.go new
+176
@@ -0,0 +1,176 @@
1 +package acmeserver
2 +
3 +import (
4 + "reflect"
5 + "testing"
6 +
7 + "github.com/smallstep/certificates/authority/policy"
8 + "github.com/smallstep/certificates/authority/provisioner"
9 +)
10 +
11 +func TestPolicyNormalizeAllowRules(t *testing.T) {
12 + type fields struct {
13 + Allow *RuleSet
14 + Deny *RuleSet
15 + AllowWildcardNames bool
16 + }
17 + tests := []struct {
18 + name string
19 + fields fields
20 + want *policy.X509NameOptions
21 + }{
22 + {
23 + name: "providing no rules results in 'nil'",
24 + fields: fields{},
25 + want: nil,
26 + },
27 + {
28 + name: "providing 'nil' Allow rules results in 'nil', regardless of Deny rules",
29 + fields: fields{
30 + Allow: nil,
31 + Deny: &RuleSet{},
32 + AllowWildcardNames: true,
33 + },
34 + want: nil,
35 + },
36 + {
37 + name: "providing empty Allow rules results in 'nil', regardless of Deny rules",
38 + fields: fields{
39 + Allow: &RuleSet{
40 + Domains: []string{},
41 + IPRanges: []string{},
42 + },
43 + },
44 + want: nil,
45 + },
46 + {
47 + name: "rules configured in Allow are returned in X509NameOptions",
48 + fields: fields{
49 + Allow: &RuleSet{
50 + Domains: []string{"example.com"},
51 + IPRanges: []string{"127.0.0.1/32"},
52 + },
53 + },
54 + want: &policy.X509NameOptions{
55 + DNSDomains: []string{"example.com"},
56 + IPRanges: []string{"127.0.0.1/32"},
57 + },
58 + },
59 + }
60 + for _, tt := range tests {
61 + t.Run(tt.name, func(t *testing.T) {
62 + p := &Policy{
63 + Allow: tt.fields.Allow,
64 + Deny: tt.fields.Deny,
65 + AllowWildcardNames: tt.fields.AllowWildcardNames,
66 + }
67 + if got := p.normalizeAllowRules(); !reflect.DeepEqual(got, tt.want) {
68 + t.Errorf("Policy.normalizeAllowRules() = %v, want %v", got, tt.want)
69 + }
70 + })
71 + }
72 +}
73 +
74 +func TestPolicy_normalizeDenyRules(t *testing.T) {
75 + type fields struct {
76 + Allow *RuleSet
77 + Deny *RuleSet
78 + AllowWildcardNames bool
79 + }
80 + tests := []struct {
81 + name string
82 + fields fields
83 + want *policy.X509NameOptions
84 + }{
85 + {
86 + name: "providing no rules results in 'nil'",
87 + fields: fields{},
88 + want: nil,
89 + },
90 + {
91 + name: "providing 'nil' Deny rules results in 'nil', regardless of Allow rules",
92 + fields: fields{
93 + Deny: nil,
94 + Allow: &RuleSet{},
95 + AllowWildcardNames: true,
96 + },
97 + want: nil,
98 + },
99 + {
100 + name: "providing empty Deny rules results in 'nil', regardless of Allow rules",
101 + fields: fields{
102 + Deny: &RuleSet{
103 + Domains: []string{},
104 + IPRanges: []string{},
105 + },
106 + },
107 + want: nil,
108 + },
109 + {
110 + name: "rules configured in Deny are returned in X509NameOptions",
111 + fields: fields{
112 + Deny: &RuleSet{
113 + Domains: []string{"example.com"},
114 + IPRanges: []string{"127.0.0.1/32"},
115 + },
116 + },
117 + want: &policy.X509NameOptions{
118 + DNSDomains: []string{"example.com"},
119 + IPRanges: []string{"127.0.0.1/32"},
120 + },
121 + },
122 + }
123 + for _, tt := range tests {
124 + t.Run(tt.name, func(t *testing.T) {
125 + p := &Policy{
126 + Allow: tt.fields.Allow,
127 + Deny: tt.fields.Deny,
128 + AllowWildcardNames: tt.fields.AllowWildcardNames,
129 + }
130 + if got := p.normalizeDenyRules(); !reflect.DeepEqual(got, tt.want) {
131 + t.Errorf("Policy.normalizeDenyRules() = %v, want %v", got, tt.want)
132 + }
133 + })
134 + }
135 +}
136 +
137 +func TestPolicy_normalizeRules(t *testing.T) {
138 + tests := []struct {
139 + name string
140 + policy *Policy
141 + want *provisioner.X509Options
142 + }{
143 + {
144 + name: "'nil' policy results in 'nil' options",
145 + policy: nil,
146 + want: nil,
147 + },
148 + {
149 + name: "'nil' Allow/Deny rules and disallowing wildcard names result in 'nil' X509Options",
150 + policy: &Policy{
151 + Allow: nil,
152 + Deny: nil,
153 + AllowWildcardNames: false,
154 + },
155 + want: nil,
156 + },
157 + {
158 + name: "'nil' Allow/Deny rules and allowing wildcard names result in 'nil' Allow/Deny rules in X509Options but allowing wildcard names in X509Options",
159 + policy: &Policy{
160 + Allow: nil,
161 + Deny: nil,
162 + AllowWildcardNames: true,
163 + },
164 + want: &provisioner.X509Options{
165 + AllowWildcardNames: true,
166 + },
167 + },
168 + }
169 + for _, tt := range tests {
170 + t.Run(tt.name, func(t *testing.T) {
171 + if got := tt.policy.normalizeRules(); !reflect.DeepEqual(got, tt.want) {
172 + t.Errorf("Policy.normalizeRules() = %v, want %v", got, tt.want)
173 + }
174 + })
175 + }
176 +}