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
+}