@marcel91200 / caddy Mirror · read-only / commits / e965b111
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, 18:15 UTC

tls: modularize trusted CA providers (#5784)

* tls: modularize client authentication trusted CA * add `omitempty` to `CARaw` * docs * initial caddyfile support * revert anything related to leaf cert validation The certs are used differently than the CA pool flow * complete caddyfile unmarshalling implementation * Caddyfile syntax documentation * enhance caddyfile parsing and documentation Apply suggestions from code review Co-authored-by: Francis Lavoie <lavofr@gmail.com> * add client_auth caddyfile tests * add caddyfile unmarshalling tests * fix and add missed adapt tests * fix rebase issue --------- Co-authored-by: Francis Lavoie <lavofr@gmail.com>

Mohammed Al Sahaf committed Jan 25, 2024 at 11:44 UTC e965b111cdf46109b4ed87e607e8dce5225105f4
10 files changed +2409 -108
caddyconfig/httpcaddyfile/builtins.go
+2 -79
@@ -15,12 +15,9 @@
15 package httpcaddyfile
16
17 import (
18 - "encoding/base64"
19 - "encoding/pem"
18 "fmt"
19 "html"
20 "net/http"
23 - "os"
21 "reflect"
22 "strconv"
23 "strings"
@@ -215,83 +212,9 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
212
213 case "client_auth":
214 cp.ClientAuthentication = &caddytls.ClientAuthentication{}
218 - for nesting := h.Nesting(); h.NextBlock(nesting); {
219 - subdir := h.Val()
220 - switch subdir {
221 - case "verifier":
222 - if !h.NextArg() {
223 - return nil, h.ArgErr()
224 - }
225 -
226 - vType := h.Val()
227 - modID := "tls.client_auth." + vType
228 - unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
229 - if err != nil {
230 - return nil, err
231 - }
232 -
233 - _, ok := unm.(caddytls.ClientCertificateVerifier)
234 - if !ok {
235 - return nil, h.Dispenser.Errf("module %s is not a caddytls.ClientCertificatVerifier", modID)
236 - }
237 -
238 - cp.ClientAuthentication.VerifiersRaw = append(cp.ClientAuthentication.VerifiersRaw, caddyconfig.JSONModuleObject(unm, "verifier", vType, h.warnings))
239 - case "mode":
240 - if !h.Args(&cp.ClientAuthentication.Mode) {
241 - return nil, h.ArgErr()
242 - }
243 - if h.NextArg() {
244 - return nil, h.ArgErr()
245 - }
246 -
247 - case "trusted_ca_cert",
248 - "trusted_leaf_cert":
249 - if !h.NextArg() {
250 - return nil, h.ArgErr()
251 - }
252 - if subdir == "trusted_ca_cert" {
253 - cp.ClientAuthentication.TrustedCACerts = append(cp.ClientAuthentication.TrustedCACerts, h.Val())
254 - } else {
255 - cp.ClientAuthentication.TrustedLeafCerts = append(cp.ClientAuthentication.TrustedLeafCerts, h.Val())
256 - }
257 -
258 - case "trusted_ca_cert_file",
259 - "trusted_leaf_cert_file":
260 - if !h.NextArg() {
261 - return nil, h.ArgErr()
262 - }
263 - filename := h.Val()
264 - certDataPEM, err := os.ReadFile(filename)
265 - if err != nil {
266 - return nil, err
267 - }
268 - // while block is not nil, we have more certificates in the file
269 - for block, rest := pem.Decode(certDataPEM); block != nil; block, rest = pem.Decode(rest) {
270 - if block.Type != "CERTIFICATE" {
271 - return nil, h.Errf("no CERTIFICATE pem block found in %s", filename)
272 - }
273 - if subdir == "trusted_ca_cert_file" {
274 - cp.ClientAuthentication.TrustedCACerts = append(
275 - cp.ClientAuthentication.TrustedCACerts,
276 - base64.StdEncoding.EncodeToString(block.Bytes),
277 - )
278 - } else {
279 - cp.ClientAuthentication.TrustedLeafCerts = append(
280 - cp.ClientAuthentication.TrustedLeafCerts,
281 - base64.StdEncoding.EncodeToString(block.Bytes),
282 - )
283 - }
284 - }
285 - // if we decoded nothing, return an error
286 - if len(cp.ClientAuthentication.TrustedCACerts) == 0 && len(cp.ClientAuthentication.TrustedLeafCerts) == 0 {
287 - return nil, h.Errf("no CERTIFICATE pem block found in %s", filename)
288 - }
289 -
290 - default:
291 - return nil, h.Errf("unknown subdirective for client_auth: %s", subdir)
292 - }
215 + if err := cp.ClientAuthentication.UnmarshalCaddyfile(h.NewFromNextSegment()); err != nil {
216 + return nil, err
217 }
294 -
218 case "alpn":
219 args := h.RemainingArgs()
220 if len(args) == 0 {
caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file-legacy.txt new
+69
@@ -0,0 +1,69 @@
1 +localhost
2 +
3 +respond "hello from localhost"
4 +tls {
5 + client_auth {
6 + mode request
7 + trusted_ca_cert_file ../caddy.ca.cer
8 + }
9 +}
10 +----------
11 +{
12 + "apps": {
13 + "http": {
14 + "servers": {
15 + "srv0": {
16 + "listen": [
17 + ":443"
18 + ],
19 + "routes": [
20 + {
21 + "match": [
22 + {
23 + "host": [
24 + "localhost"
25 + ]
26 + }
27 + ],
28 + "handle": [
29 + {
30 + "handler": "subroute",
31 + "routes": [
32 + {
33 + "handle": [
34 + {
35 + "body": "hello from localhost",
36 + "handler": "static_response"
37 + }
38 + ]
39 + }
40 + ]
41 + }
42 + ],
43 + "terminal": true
44 + }
45 + ],
46 + "tls_connection_policies": [
47 + {
48 + "match": {
49 + "sni": [
50 + "localhost"
51 + ]
52 + },
53 + "client_authentication": {
54 + "ca": {
55 + "provider": "inline",
56 + "trusted_ca_certs": [
57 + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
58 + ]
59 + },
60 + "mode": "request"
61 + }
62 + },
63 + {}
64 + ]
65 + }
66 + }
67 + }
68 + }
69 +}
\ No newline at end of file
caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file.txt
+9 -4
@@ -4,7 +4,9 @@ respond "hello from localhost"
4 tls {
5 client_auth {
6 mode request
7 - trusted_ca_cert_file ../caddy.ca.cer
7 + trust_pool file {
8 + pem_file ../caddy.ca.cer
9 + }
10 }
11 }
12 ----------
@@ -51,9 +53,12 @@ tls {
53 ]
54 },
55 "client_authentication": {
54 - "trusted_ca_certs": [
55 - "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
56 - ],
56 + "ca": {
57 + "pem_files": [
58 + "../caddy.ca.cer"
59 + ],
60 + "provider": "file"
61 + },
62 "mode": "request"
63 }
64 },
caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert-legacy.txt new
+69
@@ -0,0 +1,69 @@
1 +localhost
2 +
3 +respond "hello from localhost"
4 +tls {
5 + client_auth {
6 + mode request
7 + trusted_ca_cert MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
8 + }
9 +}
10 +----------
11 +{
12 + "apps": {
13 + "http": {
14 + "servers": {
15 + "srv0": {
16 + "listen": [
17 + ":443"
18 + ],
19 + "routes": [
20 + {
21 + "match": [
22 + {
23 + "host": [
24 + "localhost"
25 + ]
26 + }
27 + ],
28 + "handle": [
29 + {
30 + "handler": "subroute",
31 + "routes": [
32 + {
33 + "handle": [
34 + {
35 + "body": "hello from localhost",
36 + "handler": "static_response"
37 + }
38 + ]
39 + }
40 + ]
41 + }
42 + ],
43 + "terminal": true
44 + }
45 + ],
46 + "tls_connection_policies": [
47 + {
48 + "match": {
49 + "sni": [
50 + "localhost"
51 + ]
52 + },
53 + "client_authentication": {
54 + "ca": {
55 + "provider": "inline",
56 + "trusted_ca_certs": [
57 + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
58 + ]
59 + },
60 + "mode": "request"
61 + }
62 + },
63 + {}
64 + ]
65 + }
66 + }
67 + }
68 + }
69 +}
\ No newline at end of file
caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert.txt
+9 -4
@@ -4,7 +4,9 @@ respond "hello from localhost"
4 tls {
5 client_auth {
6 mode request
7 - trusted_ca_cert MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
7 + trust_pool inline {
8 + trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
9 + }
10 }
11 }
12 ----------
@@ -51,9 +53,12 @@ tls {
53 ]
54 },
55 "client_authentication": {
54 - "trusted_ca_certs": [
55 - "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
56 - ],
56 + "ca": {
57 + "provider": "inline",
58 + "trusted_ca_certs": [
59 + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
60 + ]
61 + },
62 "mode": "request"
63 }
64 },
caddytest/integration/caddyfile_adapt/tls_client_auth_inline_cert_with_leaf_trust.txt new
+75
@@ -0,0 +1,75 @@
1 +localhost
2 +
3 +respond "hello from localhost"
4 +tls {
5 + client_auth {
6 + mode request
7 + trust_pool inline {
8 + trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
9 + }
10 + trusted_leaf_cert_file ../caddy.ca.cer
11 + }
12 +}
13 +----------
14 +{
15 + "apps": {
16 + "http": {
17 + "servers": {
18 + "srv0": {
19 + "listen": [
20 + ":443"
21 + ],
22 + "routes": [
23 + {
24 + "match": [
25 + {
26 + "host": [
27 + "localhost"
28 + ]
29 + }
30 + ],
31 + "handle": [
32 + {
33 + "handler": "subroute",
34 + "routes": [
35 + {
36 + "handle": [
37 + {
38 + "body": "hello from localhost",
39 + "handler": "static_response"
40 + }
41 + ]
42 + }
43 + ]
44 + }
45 + ],
46 + "terminal": true
47 + }
48 + ],
49 + "tls_connection_policies": [
50 + {
51 + "match": {
52 + "sni": [
53 + "localhost"
54 + ]
55 + },
56 + "client_authentication": {
57 + "ca": {
58 + "provider": "inline",
59 + "trusted_ca_certs": [
60 + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
61 + ]
62 + },
63 + "trusted_leaf_certs": [
64 + "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
65 + ],
66 + "mode": "request"
67 + }
68 + },
69 + {}
70 + ]
71 + }
72 + }
73 + }
74 + }
75 +}
\ No newline at end of file
modules/caddytls/capools.go new
+820
@@ -0,0 +1,820 @@
1 +package caddytls
2 +
3 +import (
4 + "crypto/tls"
5 + "crypto/x509"
6 + "encoding/json"
7 + "errors"
8 + "fmt"
9 + "io"
10 + "net/http"
11 + "net/url"
12 + "os"
13 + "reflect"
14 +
15 + "github.com/caddyserver/certmagic"
16 +
17 + "github.com/caddyserver/caddy/v2"
18 + "github.com/caddyserver/caddy/v2/caddyconfig"
19 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
20 + "github.com/caddyserver/caddy/v2/modules/caddypki"
21 +)
22 +
23 +func init() {
24 + caddy.RegisterModule(InlineCAPool{})
25 + caddy.RegisterModule(FileCAPool{})
26 + caddy.RegisterModule(PKIRootCAPool{})
27 + caddy.RegisterModule(PKIIntermediateCAPool{})
28 + caddy.RegisterModule(StoragePool{})
29 + caddy.RegisterModule(HTTPCertPool{})
30 + caddy.RegisterModule(LazyCertPool{})
31 +}
32 +
33 +// The interface to be implemented by all guest modules part of
34 +// the namespace 'tls.ca_pool.source.'
35 +type CA interface {
36 + CertPool() *x509.CertPool
37 +}
38 +
39 +// InlineCAPool is a certificate authority pool provider coming from
40 +// a DER-encoded certificates in the config
41 +type InlineCAPool struct {
42 + // A list of base64 DER-encoded CA certificates
43 + // against which to validate client certificates.
44 + // Client certs which are not signed by any of
45 + // these CAs will be rejected.
46 + TrustedCACerts []string `json:"trusted_ca_certs,omitempty"`
47 +
48 + pool *x509.CertPool
49 +}
50 +
51 +// CaddyModule implements caddy.Module.
52 +func (icp InlineCAPool) CaddyModule() caddy.ModuleInfo {
53 + return caddy.ModuleInfo{
54 + ID: "tls.ca_pool.source.inline",
55 + New: func() caddy.Module {
56 + return new(InlineCAPool)
57 + },
58 + }
59 +}
60 +
61 +// Provision implements caddy.Provisioner.
62 +func (icp *InlineCAPool) Provision(ctx caddy.Context) error {
63 + caPool := x509.NewCertPool()
64 + for i, clientCAString := range icp.TrustedCACerts {
65 + clientCA, err := decodeBase64DERCert(clientCAString)
66 + if err != nil {
67 + return fmt.Errorf("parsing certificate at index %d: %v", i, err)
68 + }
69 + caPool.AddCert(clientCA)
70 + }
71 + icp.pool = caPool
72 +
73 + return nil
74 +}
75 +
76 +// Syntax:
77 +//
78 +// trust_pool inline {
79 +// trust_der <base64_der_cert>...
80 +// }
81 +//
82 +// The 'trust_der' directive can be specified multiple times.
83 +func (icp *InlineCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
84 + d.Next() // consume module name
85 + if d.CountRemainingArgs() > 0 {
86 + return d.ArgErr()
87 + }
88 + for d.NextBlock(0) {
89 + switch d.Val() {
90 + case "trust_der":
91 + icp.TrustedCACerts = append(icp.TrustedCACerts, d.RemainingArgs()...)
92 + default:
93 + return d.Errf("unrecognized directive: %s", d.Val())
94 + }
95 + }
96 + if len(icp.TrustedCACerts) == 0 {
97 + return d.Err("no certificates specified")
98 + }
99 + return nil
100 +}
101 +
102 +// CertPool implements CA.
103 +func (icp InlineCAPool) CertPool() *x509.CertPool {
104 + return icp.pool
105 +}
106 +
107 +// FileCAPool generates trusted root certificates pool from the designated DER and PEM file
108 +type FileCAPool struct {
109 + // TrustedCACertPEMFiles is a list of PEM file names
110 + // from which to load certificates of trusted CAs.
111 + // Client certificates which are not signed by any of
112 + // these CA certificates will be rejected.
113 + TrustedCACertPEMFiles []string `json:"pem_files,omitempty"`
114 +
115 + pool *x509.CertPool
116 +}
117 +
118 +// CaddyModule implements caddy.Module.
119 +func (FileCAPool) CaddyModule() caddy.ModuleInfo {
120 + return caddy.ModuleInfo{
121 + ID: "tls.ca_pool.source.file",
122 + New: func() caddy.Module {
123 + return new(FileCAPool)
124 + },
125 + }
126 +}
127 +
128 +// Loads and decodes the DER and pem files to generate the certificate pool
129 +func (f *FileCAPool) Provision(ctx caddy.Context) error {
130 + caPool := x509.NewCertPool()
131 + for _, pemFile := range f.TrustedCACertPEMFiles {
132 + pemContents, err := os.ReadFile(pemFile)
133 + if err != nil {
134 + return fmt.Errorf("reading %s: %v", pemFile, err)
135 + }
136 + caPool.AppendCertsFromPEM(pemContents)
137 + }
138 + f.pool = caPool
139 + return nil
140 +}
141 +
142 +// Syntax:
143 +//
144 +// trust_pool file [<pem_file>...] {
145 +// pem_file <pem_file>...
146 +// }
147 +//
148 +// The 'pem_file' directive can be specified multiple times.
149 +func (fcap *FileCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
150 + d.Next() // consume module name
151 + fcap.TrustedCACertPEMFiles = append(fcap.TrustedCACertPEMFiles, d.RemainingArgs()...)
152 + for d.NextBlock(0) {
153 + switch d.Val() {
154 + case "pem_file":
155 + fcap.TrustedCACertPEMFiles = append(fcap.TrustedCACertPEMFiles, d.RemainingArgs()...)
156 + default:
157 + return d.Errf("unrecognized directive: %s", d.Val())
158 + }
159 + }
160 + if len(fcap.TrustedCACertPEMFiles) == 0 {
161 + return d.Err("no certificates specified")
162 + }
163 + return nil
164 +}
165 +
166 +func (f FileCAPool) CertPool() *x509.CertPool {
167 + return f.pool
168 +}
169 +
170 +// PKIRootCAPool extracts the trusted root certificates from Caddy's native 'pki' app
171 +type PKIRootCAPool struct {
172 + // List of the Authority names that are configured in the `pki` app whose root certificates are trusted
173 + Authority []string `json:"authority,omitempty"`
174 +
175 + ca []*caddypki.CA
176 + pool *x509.CertPool
177 +}
178 +
179 +// CaddyModule implements caddy.Module.
180 +func (PKIRootCAPool) CaddyModule() caddy.ModuleInfo {
181 + return caddy.ModuleInfo{
182 + ID: "tls.ca_pool.source.pki_root",
183 + New: func() caddy.Module {
184 + return new(PKIRootCAPool)
185 + },
186 + }
187 +}
188 +
189 +// Loads the PKI app and load the root certificates into the certificate pool
190 +func (p *PKIRootCAPool) Provision(ctx caddy.Context) error {
191 + pkiApp := ctx.AppIfConfigured("pki")
192 + if pkiApp == nil {
193 + return fmt.Errorf("PKI app not configured")
194 + }
195 + pki := pkiApp.(*caddypki.PKI)
196 + for _, caID := range p.Authority {
197 + c, err := pki.GetCA(ctx, caID)
198 + if err != nil || c == nil {
199 + return fmt.Errorf("getting CA %s: %v", caID, err)
200 + }
201 + p.ca = append(p.ca, c)
202 + }
203 +
204 + caPool := x509.NewCertPool()
205 + for _, ca := range p.ca {
206 + caPool.AddCert(ca.RootCertificate())
207 + }
208 + p.pool = caPool
209 +
210 + return nil
211 +}
212 +
213 +// Syntax:
214 +//
215 +// trust_pool pki_root [<ca_name>...] {
216 +// authority <ca_name>...
217 +// }
218 +//
219 +// The 'authority' directive can be specified multiple times.
220 +func (pkir *PKIRootCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
221 + d.Next() // consume module name
222 + pkir.Authority = append(pkir.Authority, d.RemainingArgs()...)
223 + for nesting := d.Nesting(); d.NextBlock(nesting); {
224 + switch d.Val() {
225 + case "authority":
226 + pkir.Authority = append(pkir.Authority, d.RemainingArgs()...)
227 + default:
228 + return d.Errf("unrecognized directive: %s", d.Val())
229 + }
230 + }
231 + if len(pkir.Authority) == 0 {
232 + return d.Err("no authorities specified")
233 + }
234 + return nil
235 +}
236 +
237 +// return the certificate pool generated with root certificates from the PKI app
238 +func (p PKIRootCAPool) CertPool() *x509.CertPool {
239 + return p.pool
240 +}
241 +
242 +// PKIIntermediateCAPool extracts the trusted intermediate certificates from Caddy's native 'pki' app
243 +type PKIIntermediateCAPool struct {
244 + // List of the Authority names that are configured in the `pki` app whose intermediate certificates are trusted
245 + Authority []string `json:"authority,omitempty"`
246 +
247 + ca []*caddypki.CA
248 + pool *x509.CertPool
249 +}
250 +
251 +// CaddyModule implements caddy.Module.
252 +func (PKIIntermediateCAPool) CaddyModule() caddy.ModuleInfo {
253 + return caddy.ModuleInfo{
254 + ID: "tls.ca_pool.source.pki_intermediate",
255 + New: func() caddy.Module {
256 + return new(PKIIntermediateCAPool)
257 + },
258 + }
259 +}
260 +
261 +// Loads the PKI app and load the intermediate certificates into the certificate pool
262 +func (p *PKIIntermediateCAPool) Provision(ctx caddy.Context) error {
263 + pkiApp := ctx.AppIfConfigured("pki")
264 + if pkiApp == nil {
265 + return fmt.Errorf("PKI app not configured")
266 + }
267 + pki := pkiApp.(*caddypki.PKI)
268 + for _, caID := range p.Authority {
269 + c, err := pki.GetCA(ctx, caID)
270 + if err != nil || c == nil {
271 + return fmt.Errorf("getting CA %s: %v", caID, err)
272 + }
273 + p.ca = append(p.ca, c)
274 + }
275 +
276 + caPool := x509.NewCertPool()
277 + for _, ca := range p.ca {
278 + caPool.AddCert(ca.IntermediateCertificate())
279 + }
280 + p.pool = caPool
281 + return nil
282 +}
283 +
284 +// Syntax:
285 +//
286 +// trust_pool pki_intermediate [<ca_name>...] {
287 +// authority <ca_name>...
288 +// }
289 +//
290 +// The 'authority' directive can be specified multiple times.
291 +func (pic *PKIIntermediateCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
292 + d.Next() // consume module name
293 + pic.Authority = append(pic.Authority, d.RemainingArgs()...)
294 + for nesting := d.Nesting(); d.NextBlock(nesting); {
295 + switch d.Val() {
296 + case "authority":
297 + pic.Authority = append(pic.Authority, d.RemainingArgs()...)
298 + default:
299 + return d.Errf("unrecognized directive: %s", d.Val())
300 + }
301 + }
302 + if len(pic.Authority) == 0 {
303 + return d.Err("no authorities specified")
304 + }
305 + return nil
306 +}
307 +
308 +// return the certificate pool generated with intermediate certificates from the PKI app
309 +func (p PKIIntermediateCAPool) CertPool() *x509.CertPool {
310 + return p.pool
311 +}
312 +
313 +// StoragePool extracts the trusted certificates root from Caddy storage
314 +type StoragePool struct {
315 + // The storage module where the trusted root certificates are stored. Absent
316 + // explicit storage implies the use of Caddy default storage.
317 + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
318 +
319 + // The storage key/index to the location of the certificates
320 + PEMKeys []string `json:"pem_keys,omitempty"`
321 +
322 + storage certmagic.Storage
323 + pool *x509.CertPool
324 +}
325 +
326 +// CaddyModule implements caddy.Module.
327 +func (StoragePool) CaddyModule() caddy.ModuleInfo {
328 + return caddy.ModuleInfo{
329 + ID: "tls.ca_pool.source.storage",
330 + New: func() caddy.Module {
331 + return new(StoragePool)
332 + },
333 + }
334 +}
335 +
336 +// Provision implements caddy.Provisioner.
337 +func (ca *StoragePool) Provision(ctx caddy.Context) error {
338 + if ca.StorageRaw != nil {
339 + val, err := ctx.LoadModule(ca, "StorageRaw")
340 + if err != nil {
341 + return fmt.Errorf("loading storage module: %v", err)
342 + }
343 + cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
344 + if err != nil {
345 + return fmt.Errorf("creating storage configuration: %v", err)
346 + }
347 + ca.storage = cmStorage
348 + }
349 + if ca.storage == nil {
350 + ca.storage = ctx.Storage()
351 + }
352 + if len(ca.PEMKeys) == 0 {
353 + return fmt.Errorf("no PEM keys specified")
354 + }
355 + caPool := x509.NewCertPool()
356 + for _, caID := range ca.PEMKeys {
357 + bs, err := ca.storage.Load(ctx, caID)
358 + if err != nil {
359 + return fmt.Errorf("error loading cert '%s' from storage: %s", caID, err)
360 + }
361 + if !caPool.AppendCertsFromPEM(bs) {
362 + return fmt.Errorf("failed to add certificate '%s' to pool", caID)
363 + }
364 + }
365 + ca.pool = caPool
366 +
367 + return nil
368 +}
369 +
370 +// Syntax:
371 +//
372 +// trust_pool storage [<storage_keys>...] {
373 +// storage <storage_module>
374 +// keys <storage_keys>...
375 +// }
376 +//
377 +// The 'keys' directive can be specified multiple times.
378 +// The'storage' directive is optional and defaults to the default storage module.
379 +func (sp *StoragePool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
380 + d.Next() // consume module name
381 + sp.PEMKeys = append(sp.PEMKeys, d.RemainingArgs()...)
382 + for nesting := d.Nesting(); d.NextBlock(nesting); {
383 + switch d.Val() {
384 + case "storage":
385 + if sp.StorageRaw != nil {
386 + return d.Err("storage module already set")
387 + }
388 + if !d.NextArg() {
389 + return d.ArgErr()
390 + }
391 + modStem := d.Val()
392 + modID := "caddy.storage." + modStem
393 + unm, err := caddyfile.UnmarshalModule(d, modID)
394 + if err != nil {
395 + return err
396 + }
397 + storage, ok := unm.(caddy.StorageConverter)
398 + if !ok {
399 + return d.Errf("module %s is not a caddy.StorageConverter", modID)
400 + }
401 + sp.StorageRaw = caddyconfig.JSONModuleObject(storage, "module", modStem, nil)
402 + case "keys":
403 + sp.PEMKeys = append(sp.PEMKeys, d.RemainingArgs()...)
404 + default:
405 + return d.Errf("unrecognized directive: %s", d.Val())
406 + }
407 + }
408 + return nil
409 +}
410 +
411 +func (p StoragePool) CertPool() *x509.CertPool {
412 + return p.pool
413 +}
414 +
415 +// TLSConfig holds configuration related to the TLS configuration for the
416 +// transport/client.
417 +// copied from with minor modifications: modules/caddyhttp/reverseproxy/httptransport.go
418 +type TLSConfig struct {
419 + // Provides the guest module that provides the trusted certificate authority (CA) certificates
420 + CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
421 +
422 + // If true, TLS verification of server certificates will be disabled.
423 + // This is insecure and may be removed in the future. Do not use this
424 + // option except in testing or local development environments.
425 + InsecureSkipVerify bool `json:"insecure_skip_verify,omitempty"`
426 +
427 + // The duration to allow a TLS handshake to a server. Default: No timeout.
428 + HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"`
429 +
430 + // The server name used when verifying the certificate received in the TLS
431 + // handshake. By default, this will use the upstream address' host part.
432 + // You only need to override this if your upstream address does not match the
433 + // certificate the upstream is likely to use. For example if the upstream
434 + // address is an IP address, then you would need to configure this to the
435 + // hostname being served by the upstream server. Currently, this does not
436 + // support placeholders because the TLS config is not provisioned on each
437 + // connection, so a static value must be used.
438 + ServerName string `json:"server_name,omitempty"`
439 +
440 + // TLS renegotiation level. TLS renegotiation is the act of performing
441 + // subsequent handshakes on a connection after the first.
442 + // The level can be:
443 + // - "never": (the default) disables renegotiation.
444 + // - "once": allows a remote server to request renegotiation once per connection.
445 + // - "freely": allows a remote server to repeatedly request renegotiation.
446 + Renegotiation string `json:"renegotiation,omitempty"`
447 +}
448 +
449 +func (t *TLSConfig) unmarshalCaddyfile(d *caddyfile.Dispenser) error {
450 + for nesting := d.Nesting(); d.NextBlock(nesting); {
451 + switch d.Val() {
452 + case "ca":
453 + if !d.NextArg() {
454 + return d.ArgErr()
455 + }
456 + modStem := d.Val()
457 + modID := "tls.ca_pool.source." + modStem
458 + unm, err := caddyfile.UnmarshalModule(d, modID)
459 + if err != nil {
460 + return err
461 + }
462 + ca, ok := unm.(CA)
463 + if !ok {
464 + return d.Errf("module %s is not a caddytls.CA", modID)
465 + }
466 + t.CARaw = caddyconfig.JSONModuleObject(ca, "provider", modStem, nil)
467 + case "insecure_skip_verify":
468 + t.InsecureSkipVerify = true
469 + case "handshake_timeout":
470 + if !d.NextArg() {
471 + return d.ArgErr()
472 + }
473 + dur, err := caddy.ParseDuration(d.Val())
474 + if err != nil {
475 + return d.Errf("bad timeout value '%s': %v", d.Val(), err)
476 + }
477 + t.HandshakeTimeout = caddy.Duration(dur)
478 + case "server_name":
479 + if !d.Args(&t.ServerName) {
480 + return d.ArgErr()
481 + }
482 + case "renegotiation":
483 + if !d.Args(&t.Renegotiation) {
484 + return d.ArgErr()
485 + }
486 + switch t.Renegotiation {
487 + case "never", "once", "freely":
488 + continue
489 + default:
490 + t.Renegotiation = ""
491 + return d.Errf("unrecognized renegotiation level: %s", t.Renegotiation)
492 + }
493 + default:
494 + return d.Errf("unrecognized directive: %s", d.Val())
495 + }
496 + }
497 + return nil
498 +}
499 +
500 +// MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
501 +// If there is no custom TLS configuration, a nil config may be returned.
502 +// copied from with minor modifications: modules/caddyhttp/reverseproxy/httptransport.go
503 +func (t TLSConfig) makeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
504 + repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
505 + if repl == nil {
506 + repl = caddy.NewReplacer()
507 + }
508 + cfg := new(tls.Config)
509 +
510 + if t.CARaw != nil {
511 + caRaw, err := ctx.LoadModule(t, "CARaw")
512 + if err != nil {
513 + return nil, err
514 + }
515 + ca := caRaw.(CA)
516 + cfg.RootCAs = ca.CertPool()
517 + }
518 +
519 + // Renegotiation
520 + switch t.Renegotiation {
521 + case "never", "":
522 + cfg.Renegotiation = tls.RenegotiateNever
523 + case "once":
524 + cfg.Renegotiation = tls.RenegotiateOnceAsClient
525 + case "freely":
526 + cfg.Renegotiation = tls.RenegotiateFreelyAsClient
527 + default:
528 + return nil, fmt.Errorf("invalid TLS renegotiation level: %v", t.Renegotiation)
529 + }
530 +
531 + // override for the server name used verify the TLS handshake
532 + cfg.ServerName = repl.ReplaceKnown(cfg.ServerName, "")
533 +
534 + // throw all security out the window
535 + cfg.InsecureSkipVerify = t.InsecureSkipVerify
536 +
537 + // only return a config if it's not empty
538 + if reflect.DeepEqual(cfg, new(tls.Config)) {
539 + return nil, nil
540 + }
541 +
542 + return cfg, nil
543 +}
544 +
545 +// The HTTPCertPool fetches the trusted root certificates from HTTP(S)
546 +// endpoints. The TLS connection properties can be customized, including custom
547 +// trusted root certificate. One example usage of this module is to get the trusted
548 +// certificates from another Caddy instance that is running the PKI app and ACME server.
549 +type HTTPCertPool struct {
550 + // the list of URLs that respond with PEM-encoded certificates to trust.
551 + Endpoints []string `json:"endpoints,omitempty"`
552 +
553 + // Customize the TLS connection knobs to used during the HTTP call
554 + TLS *TLSConfig `json:"tls,omitempty"`
555 +
556 + pool *x509.CertPool
557 +}
558 +
559 +// CaddyModule implements caddy.Module.
560 +func (HTTPCertPool) CaddyModule() caddy.ModuleInfo {
561 + return caddy.ModuleInfo{
562 + ID: "tls.ca_pool.source.http",
563 + New: func() caddy.Module {
564 + return new(HTTPCertPool)
565 + },
566 + }
567 +}
568 +
569 +// Provision implements caddy.Provisioner.
570 +func (hcp *HTTPCertPool) Provision(ctx caddy.Context) error {
571 + caPool := x509.NewCertPool()
572 +
573 + customTransport := http.DefaultTransport.(*http.Transport).Clone()
574 + if hcp.TLS != nil {
575 + tlsConfig, err := hcp.TLS.makeTLSClientConfig(ctx)
576 + if err != nil {
577 + return err
578 + }
579 + customTransport.TLSClientConfig = tlsConfig
580 + }
581 +
582 + var httpClient *http.Client
583 + *httpClient = *http.DefaultClient
584 + httpClient.Transport = customTransport
585 +
586 + for _, uri := range hcp.Endpoints {
587 + req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
588 + if err != nil {
589 + return err
590 + }
591 + res, err := httpClient.Do(req)
592 + if err != nil {
593 + return err
594 + }
595 + pembs, err := io.ReadAll(res.Body)
596 + res.Body.Close()
597 + if err != nil {
598 + return err
599 + }
600 + if !caPool.AppendCertsFromPEM(pembs) {
601 + return fmt.Errorf("failed to add certs from URL: %s", uri)
602 + }
603 + }
604 + hcp.pool = caPool
605 + return nil
606 +}
607 +
608 +// Syntax:
609 +//
610 +// trust_pool http [<endpoints...>] {
611 +// endpoints <endpoints...>
612 +// tls <tls_config>
613 +// }
614 +//
615 +// tls_config:
616 +//
617 +// ca <ca_module>
618 +// insecure_skip_verify
619 +// handshake_timeout <duration>
620 +// server_name <name>
621 +// renegotiation <never|once|freely>
622 +//
623 +// <ca_module> is the name of the CA module to source the trust
624 +//
625 +// certificate pool and follows the syntax of the named CA module.
626 +func (hcp *HTTPCertPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
627 + d.Next() // consume module name
628 + hcp.Endpoints = append(hcp.Endpoints, d.RemainingArgs()...)
629 + for nesting := d.Nesting(); d.NextBlock(nesting); {
630 + switch d.Val() {
631 + case "endpoints":
632 + if d.CountRemainingArgs() == 0 {
633 + return d.ArgErr()
634 + }
635 + hcp.Endpoints = append(hcp.Endpoints, d.RemainingArgs()...)
636 + case "tls":
637 + if hcp.TLS != nil {
638 + return d.Err("tls block already defined")
639 + }
640 + hcp.TLS = new(TLSConfig)
641 + if err := hcp.TLS.unmarshalCaddyfile(d); err != nil {
642 + return err
643 + }
644 + default:
645 + return d.Errf("unrecognized directive: %s", d.Val())
646 + }
647 + }
648 +
649 + return nil
650 +}
651 +
652 +// report error if the endpoints are not valid URLs
653 +func (hcp HTTPCertPool) Validate() (err error) {
654 + for _, u := range hcp.Endpoints {
655 + _, e := url.Parse(u)
656 + if e != nil {
657 + err = errors.Join(err, e)
658 + }
659 + }
660 + return err
661 +}
662 +
663 +// CertPool return the certificate pool generated from the HTTP responses
664 +func (hcp HTTPCertPool) CertPool() *x509.CertPool {
665 + return hcp.pool
666 +}
667 +
668 +// LazyCertPool defers the generation of the certificate pool from the
669 +// guest module to demand-time rather than at provisionig time. The gain of the
670 +// lazy load adds a risk of failure to load the certificates at demand time
671 +// because the validation that's typically done at provisioning is deferred.
672 +// The validation can be enforced to run before runtime by setting
673 +// `EagerValidation`/`eager_validation` to `true`. It is the operator's responsibility
674 +// to ensure the resources are available if `EagerValidation`/`eager_validation`
675 +// is set to `true`. The module also incurs performance cost at every demand.
676 +type LazyCertPool struct {
677 + // Provides the guest module that provides the trusted certificate authority (CA) certificates
678 + CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
679 +
680 + // Whether the validation step should try to load and provision the guest module to validate
681 + // the correctness of the configuration. Depeneding on the type of the guest module,
682 + // the resources may not be available at validation time. It is the
683 + // operator's responsibility to ensure the resources are available if `EagerValidation`/`eager_validation`
684 + // is set to `true`.
685 + EagerValidation bool `json:"eager_validation,omitempty"`
686 +
687 + ctx caddy.Context
688 +}
689 +
690 +// CaddyModule implements caddy.Module.
691 +func (LazyCertPool) CaddyModule() caddy.ModuleInfo {
692 + return caddy.ModuleInfo{
693 + ID: "tls.ca_pool.source.lazy",
694 + New: func() caddy.Module {
695 + return new(LazyCertPool)
696 + },
697 + }
698 +}
699 +
700 +// Provision implements caddy.Provisioner.
701 +func (lcp *LazyCertPool) Provision(ctx caddy.Context) error {
702 + if len(lcp.CARaw) == 0 {
703 + return fmt.Errorf("missing backing CA source")
704 + }
705 + lcp.ctx = ctx
706 + return nil
707 +}
708 +
709 +// Syntax:
710 +//
711 +// trust_pool lazy {
712 +// backend <ca_module>
713 +// eager_validation
714 +// }
715 +//
716 +// The `backend` directive specifies the CA module to use to provision the
717 +// certificate pool. The `eager_validation` directive specifies that the
718 +// validation step should try to load and provision the guest module to validate
719 +// the correctness of the configuration. Depeneding on the type of the guest module,
720 +// the resources may not be available at validation time. It is the
721 +// operator's responsibility to ensure the resources are available if `EagerValidation`/`eager_validation`
722 +// is set to `true`.
723 +//
724 +// The `backend` directive is required.
725 +func (lcp *LazyCertPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
726 + d.Next() // consume module name
727 + for nesting := d.Nesting(); d.NextBlock(nesting); {
728 + switch d.Val() {
729 + case "backend":
730 + if lcp.CARaw != nil {
731 + return d.Err("backend block already defined")
732 + }
733 + if !d.NextArg() {
734 + return d.ArgErr()
735 + }
736 + modStem := d.Val()
737 + modID := "tls.ca_pool.source." + modStem
738 + unm, err := caddyfile.UnmarshalModule(d, modID)
739 + if err != nil {
740 + return err
741 + }
742 + backend, ok := unm.(CA)
743 + if !ok {
744 + return d.Errf("module %s is not a caddytls.CA", modID)
745 + }
746 + lcp.CARaw = caddyconfig.JSONModuleObject(backend, "provider", modStem, nil)
747 + case "eager_validation":
748 + lcp.EagerValidation = true
749 + default:
750 + return d.Errf("unrecognized directive: %s", d.Val())
751 + }
752 + }
753 + if lcp.CARaw == nil {
754 + return d.Err("backend block is required")
755 + }
756 + return nil
757 +}
758 +
759 +// If EagerValidation is `true`, it attempts to load and provision the guest module
760 +// to ensure the guesst module's configuration is correct. Depeneding on the type of the
761 +// guest module, the resources may not be available at validation time. It is the
762 +// operator's responsibility to ensure the resources are available if `EagerValidation` is
763 +// set to `true`.
764 +func (lcp LazyCertPool) Validate() error {
765 + if lcp.EagerValidation {
766 + _, err := lcp.ctx.LoadModule(lcp, "CARaw")
767 + return err
768 + }
769 + return nil
770 +}
771 +
772 +// CertPool loads the guest module and returns the CertPool from there
773 +// TODO: Cache?
774 +func (lcp LazyCertPool) CertPool() *x509.CertPool {
775 + caRaw, err := lcp.ctx.LoadModule(lcp, "CARaw")
776 + if err != nil {
777 + return nil
778 + }
779 + ca := caRaw.(CA)
780 + return ca.CertPool()
781 +}
782 +
783 +var (
784 + _ caddy.Module = (*InlineCAPool)(nil)
785 + _ caddy.Provisioner = (*InlineCAPool)(nil)
786 + _ CA = (*InlineCAPool)(nil)
787 + _ caddyfile.Unmarshaler = (*InlineCAPool)(nil)
788 +
789 + _ caddy.Module = (*FileCAPool)(nil)
790 + _ caddy.Provisioner = (*FileCAPool)(nil)
791 + _ CA = (*FileCAPool)(nil)
792 + _ caddyfile.Unmarshaler = (*FileCAPool)(nil)
793 +
794 + _ caddy.Module = (*PKIRootCAPool)(nil)
795 + _ caddy.Provisioner = (*PKIRootCAPool)(nil)
796 + _ CA = (*PKIRootCAPool)(nil)
797 + _ caddyfile.Unmarshaler = (*PKIRootCAPool)(nil)
798 +
799 + _ caddy.Module = (*PKIIntermediateCAPool)(nil)
800 + _ caddy.Provisioner = (*PKIIntermediateCAPool)(nil)
801 + _ CA = (*PKIIntermediateCAPool)(nil)
802 + _ caddyfile.Unmarshaler = (*PKIIntermediateCAPool)(nil)
803 +
804 + _ caddy.Module = (*StoragePool)(nil)
805 + _ caddy.Provisioner = (*StoragePool)(nil)
806 + _ CA = (*StoragePool)(nil)
807 + _ caddyfile.Unmarshaler = (*StoragePool)(nil)
808 +
809 + _ caddy.Module = (*HTTPCertPool)(nil)
810 + _ caddy.Provisioner = (*HTTPCertPool)(nil)
811 + _ caddy.Validator = (*HTTPCertPool)(nil)
812 + _ CA = (*HTTPCertPool)(nil)
813 + _ caddyfile.Unmarshaler = (*HTTPCertPool)(nil)
814 +
815 + _ caddy.Module = (*LazyCertPool)(nil)
816 + _ caddy.Provisioner = (*LazyCertPool)(nil)
817 + _ caddy.Validator = (*LazyCertPool)(nil)
818 + _ CA = (*LazyCertPool)(nil)
819 + _ caddyfile.Unmarshaler = (*LazyCertPool)(nil)
820 +)
modules/caddytls/capools_test.go new
+892
@@ -0,0 +1,892 @@
1 +package caddytls
2 +
3 +import (
4 + "encoding/json"
5 + "fmt"
6 + "reflect"
7 + "testing"
8 + "time"
9 +
10 + "github.com/caddyserver/caddy/v2"
11 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
12 + _ "github.com/caddyserver/caddy/v2/modules/filestorage"
13 +)
14 +
15 +const (
16 + test_der_1 = `MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==`
17 + test_cert_file_1 = "../../caddytest/caddy.ca.cer"
18 +)
19 +
20 +func TestInlineCAPoolUnmarshalCaddyfile(t *testing.T) {
21 + type args struct {
22 + d *caddyfile.Dispenser
23 + }
24 + tests := []struct {
25 + name string
26 + args args
27 + expected InlineCAPool
28 + wantErr bool
29 + }{
30 + {
31 + name: "configuring no certificatest produces an error",
32 + args: args{
33 + d: caddyfile.NewTestDispenser(`
34 + inline {
35 + }
36 + `),
37 + },
38 + wantErr: true,
39 + },
40 + {
41 + name: "configuring certificates as arguments in-line produces an error",
42 + args: args{
43 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
44 + inline %s
45 + `, test_der_1)),
46 + },
47 + wantErr: true,
48 + },
49 + {
50 + name: "single cert",
51 + args: args{
52 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
53 + inline {
54 + trust_der %s
55 + }
56 + `, test_der_1)),
57 + },
58 + expected: InlineCAPool{
59 + TrustedCACerts: []string{test_der_1},
60 + },
61 + wantErr: false,
62 + },
63 + {
64 + name: "multiple certs in one line",
65 + args: args{
66 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
67 + inline {
68 + trust_der %s %s
69 + }
70 + `, test_der_1, test_der_1),
71 + ),
72 + },
73 + expected: InlineCAPool{
74 + TrustedCACerts: []string{test_der_1, test_der_1},
75 + },
76 + },
77 + {
78 + name: "multiple certs in multiple lines",
79 + args: args{
80 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
81 + inline {
82 + trust_der %s
83 + trust_der %s
84 + }
85 + `, test_der_1, test_der_1)),
86 + },
87 + expected: InlineCAPool{
88 + TrustedCACerts: []string{test_der_1, test_der_1},
89 + },
90 + },
91 + }
92 + for _, tt := range tests {
93 + t.Run(tt.name, func(t *testing.T) {
94 + icp := &InlineCAPool{}
95 + if err := icp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
96 + t.Errorf("InlineCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
97 + }
98 + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, icp) {
99 + t.Errorf("InlineCAPool.UnmarshalCaddyfile() = %v, want %v", icp, tt.expected)
100 + }
101 + })
102 + }
103 +}
104 +
105 +func TestFileCAPoolUnmarshalCaddyfile(t *testing.T) {
106 + type args struct {
107 + d *caddyfile.Dispenser
108 + }
109 + tests := []struct {
110 + name string
111 + expected FileCAPool
112 + args args
113 + wantErr bool
114 + }{
115 + {
116 + name: "configuring no certificatest produces an error",
117 + args: args{
118 + d: caddyfile.NewTestDispenser(`
119 + file {
120 + }
121 + `),
122 + },
123 + wantErr: true,
124 + },
125 + {
126 + name: "configuring certificates as arguments in-line produces an error",
127 + args: args{
128 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
129 + file %s
130 + `, test_cert_file_1)),
131 + },
132 + expected: FileCAPool{
133 + TrustedCACertPEMFiles: []string{test_cert_file_1},
134 + },
135 + },
136 + {
137 + name: "single cert",
138 + args: args{
139 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
140 + file {
141 + pem_file %s
142 + }
143 + `, test_cert_file_1)),
144 + },
145 + expected: FileCAPool{
146 + TrustedCACertPEMFiles: []string{test_cert_file_1},
147 + },
148 + wantErr: false,
149 + },
150 + {
151 + name: "multiple certs inline and in-block are merged",
152 + args: args{
153 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
154 + file %s {
155 + pem_file %s
156 + }
157 + `, test_cert_file_1, test_cert_file_1)),
158 + },
159 + expected: FileCAPool{
160 + TrustedCACertPEMFiles: []string{test_cert_file_1, test_cert_file_1},
161 + },
162 + wantErr: false,
163 + },
164 + {
165 + name: "multiple certs in one line",
166 + args: args{
167 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
168 + file {
169 + pem_file %s %s
170 + }
171 + `, test_der_1, test_der_1),
172 + ),
173 + },
174 + expected: FileCAPool{
175 + TrustedCACertPEMFiles: []string{test_der_1, test_der_1},
176 + },
177 + },
178 + {
179 + name: "multiple certs in multiple lines",
180 + args: args{
181 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
182 + file {
183 + pem_file %s
184 + pem_file %s
185 + }
186 + `, test_cert_file_1, test_cert_file_1)),
187 + },
188 + expected: FileCAPool{
189 + TrustedCACertPEMFiles: []string{test_cert_file_1, test_cert_file_1},
190 + },
191 + },
192 + }
193 + for _, tt := range tests {
194 + t.Run(tt.name, func(t *testing.T) {
195 + fcap := &FileCAPool{}
196 + if err := fcap.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
197 + t.Errorf("FileCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
198 + }
199 + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, fcap) {
200 + t.Errorf("FileCAPool.UnmarshalCaddyfile() = %v, want %v", fcap, tt.expected)
201 + }
202 + })
203 + }
204 +}
205 +
206 +func TestPKIRootCAPoolUnmarshalCaddyfile(t *testing.T) {
207 + type args struct {
208 + d *caddyfile.Dispenser
209 + }
210 + tests := []struct {
211 + name string
212 + expected PKIRootCAPool
213 + args args
214 + wantErr bool
215 + }{
216 + {
217 + name: "configuring no certificatest produces an error",
218 + args: args{
219 + d: caddyfile.NewTestDispenser(`
220 + pki_root {
221 + }
222 + `),
223 + },
224 + wantErr: true,
225 + },
226 + {
227 + name: "single authority as arguments in-line",
228 + args: args{
229 + d: caddyfile.NewTestDispenser(`
230 + pki_root ca_1
231 + `),
232 + },
233 + expected: PKIRootCAPool{
234 + Authority: []string{"ca_1"},
235 + },
236 + },
237 + {
238 + name: "multiple authorities as arguments in-line",
239 + args: args{
240 + d: caddyfile.NewTestDispenser(`
241 + pki_root ca_1 ca_2
242 + `),
243 + },
244 + expected: PKIRootCAPool{
245 + Authority: []string{"ca_1", "ca_2"},
246 + },
247 + },
248 + {
249 + name: "single authority in block",
250 + args: args{
251 + d: caddyfile.NewTestDispenser(`
252 + pki_root {
253 + authority ca_1
254 + }`),
255 + },
256 + expected: PKIRootCAPool{
257 + Authority: []string{"ca_1"},
258 + },
259 + wantErr: false,
260 + },
261 + {
262 + name: "multiple authorities in one line",
263 + args: args{
264 + d: caddyfile.NewTestDispenser(`
265 + pki_root {
266 + authority ca_1 ca_2
267 + }`),
268 + },
269 + expected: PKIRootCAPool{
270 + Authority: []string{"ca_1", "ca_2"},
271 + },
272 + },
273 + {
274 + name: "multiple authorities in multiple lines",
275 + args: args{
276 + d: caddyfile.NewTestDispenser(`
277 + pki_root {
278 + authority ca_1
279 + authority ca_2
280 + }`),
281 + },
282 + expected: PKIRootCAPool{
283 + Authority: []string{"ca_1", "ca_2"},
284 + },
285 + },
286 + }
287 + for _, tt := range tests {
288 + t.Run(tt.name, func(t *testing.T) {
289 + pkir := &PKIRootCAPool{}
290 + if err := pkir.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
291 + t.Errorf("PKIRootCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
292 + }
293 + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, pkir) {
294 + t.Errorf("PKIRootCAPool.UnmarshalCaddyfile() = %v, want %v", pkir, tt.expected)
295 + }
296 + })
297 + }
298 +}
299 +
300 +func TestPKIIntermediateCAPoolUnmarshalCaddyfile(t *testing.T) {
301 + type args struct {
302 + d *caddyfile.Dispenser
303 + }
304 + tests := []struct {
305 + name string
306 + expected PKIIntermediateCAPool
307 + args args
308 + wantErr bool
309 + }{
310 + {
311 + name: "configuring no certificatest produces an error",
312 + args: args{
313 + d: caddyfile.NewTestDispenser(`
314 + pki_intermediate {
315 + }`),
316 + },
317 + wantErr: true,
318 + },
319 + {
320 + name: "single authority as arguments in-line",
321 + args: args{
322 + d: caddyfile.NewTestDispenser(`pki_intermediate ca_1`),
323 + },
324 + expected: PKIIntermediateCAPool{
325 + Authority: []string{"ca_1"},
326 + },
327 + },
328 + {
329 + name: "multiple authorities as arguments in-line",
330 + args: args{
331 + d: caddyfile.NewTestDispenser(`pki_intermediate ca_1 ca_2`),
332 + },
333 + expected: PKIIntermediateCAPool{
334 + Authority: []string{"ca_1", "ca_2"},
335 + },
336 + },
337 + {
338 + name: "single authority in block",
339 + args: args{
340 + d: caddyfile.NewTestDispenser(`
341 + pki_intermediate {
342 + authority ca_1
343 + }`),
344 + },
345 + expected: PKIIntermediateCAPool{
346 + Authority: []string{"ca_1"},
347 + },
348 + wantErr: false,
349 + },
350 + {
351 + name: "multiple authorities in one line",
352 + args: args{
353 + d: caddyfile.NewTestDispenser(`
354 + pki_intermediate {
355 + authority ca_1 ca_2
356 + }`),
357 + },
358 + expected: PKIIntermediateCAPool{
359 + Authority: []string{"ca_1", "ca_2"},
360 + },
361 + },
362 + {
363 + name: "multiple authorities in multiple lines",
364 + args: args{
365 + d: caddyfile.NewTestDispenser(`
366 + pki_intermediate {
367 + authority ca_1
368 + authority ca_2
369 + }`),
370 + },
371 + expected: PKIIntermediateCAPool{
372 + Authority: []string{"ca_1", "ca_2"},
373 + },
374 + },
375 + }
376 + for _, tt := range tests {
377 + t.Run(tt.name, func(t *testing.T) {
378 + pic := &PKIIntermediateCAPool{}
379 + if err := pic.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
380 + t.Errorf("PKIIntermediateCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
381 + }
382 + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, pic) {
383 + t.Errorf("PKIIntermediateCAPool.UnmarshalCaddyfile() = %v, want %v", pic, tt.expected)
384 + }
385 + })
386 + }
387 +}
388 +
389 +func TestStoragePoolUnmarshalCaddyfile(t *testing.T) {
390 + type args struct {
391 + d *caddyfile.Dispenser
392 + }
393 + tests := []struct {
394 + name string
395 + args args
396 + expected StoragePool
397 + wantErr bool
398 + }{
399 + {
400 + name: "empty block",
401 + args: args{
402 + d: caddyfile.NewTestDispenser(`storage {
403 + }`),
404 + },
405 + expected: StoragePool{},
406 + wantErr: false,
407 + },
408 + {
409 + name: "providing single storage key inline",
410 + args: args{
411 + d: caddyfile.NewTestDispenser(`storage key-1`),
412 + },
413 + expected: StoragePool{
414 + PEMKeys: []string{"key-1"},
415 + },
416 + wantErr: false,
417 + },
418 + {
419 + name: "providing multiple storage keys inline",
420 + args: args{
421 + d: caddyfile.NewTestDispenser(`storage key-1 key-2`),
422 + },
423 + expected: StoragePool{
424 + PEMKeys: []string{"key-1", "key-2"},
425 + },
426 + wantErr: false,
427 + },
428 + {
429 + name: "providing keys inside block without specifying storage type",
430 + args: args{
431 + d: caddyfile.NewTestDispenser(`
432 + storage {
433 + keys key-1 key-2
434 + }
435 + `),
436 + },
437 + expected: StoragePool{
438 + PEMKeys: []string{"key-1", "key-2"},
439 + },
440 + wantErr: false,
441 + },
442 + {
443 + name: "providing keys in-line and inside block merges them",
444 + args: args{
445 + d: caddyfile.NewTestDispenser(`storage key-1 key-2 key-3 {
446 + keys key-4 key-5
447 + }`),
448 + },
449 + expected: StoragePool{
450 + PEMKeys: []string{"key-1", "key-2", "key-3", "key-4", "key-5"},
451 + },
452 + wantErr: false,
453 + },
454 + {
455 + name: "specifying storage type in block",
456 + args: args{
457 + d: caddyfile.NewTestDispenser(`storage {
458 + storage file_system /var/caddy/storage
459 + }`),
460 + },
461 + expected: StoragePool{
462 + StorageRaw: json.RawMessage(`{"module":"file_system","root":"/var/caddy/storage"}`),
463 + },
464 + wantErr: false,
465 + },
466 + }
467 + for _, tt := range tests {
468 + t.Run(tt.name, func(t *testing.T) {
469 + sp := &StoragePool{}
470 + if err := sp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
471 + t.Errorf("StoragePool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
472 + }
473 + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, sp) {
474 + t.Errorf("StoragePool.UnmarshalCaddyfile() = %s, want %s", sp.StorageRaw, tt.expected.StorageRaw)
475 + }
476 + })
477 + }
478 +}
479 +
480 +func TestTLSConfig_unmarshalCaddyfile(t *testing.T) {
481 + type args struct {
482 + d *caddyfile.Dispenser
483 + }
484 + tests := []struct {
485 + name string
486 + args args
487 + expected TLSConfig
488 + wantErr bool
489 + }{
490 + {
491 + name: "no arguments is valid",
492 + args: args{
493 + d: caddyfile.NewTestDispenser(` {
494 + }`),
495 + },
496 + expected: TLSConfig{},
497 + },
498 + {
499 + name: "setting 'renegotiation' to 'never' is valid",
500 + args: args{
501 + d: caddyfile.NewTestDispenser(` {
502 + renegotiation never
503 + }`),
504 + },
505 + expected: TLSConfig{
506 + Renegotiation: "never",
507 + },
508 + },
509 + {
510 + name: "setting 'renegotiation' to 'once' is valid",
511 + args: args{
512 + d: caddyfile.NewTestDispenser(` {
513 + renegotiation once
514 + }`),
515 + },
516 + expected: TLSConfig{
517 + Renegotiation: "once",
518 + },
519 + },
520 + {
521 + name: "setting 'renegotiation' to 'freely' is valid",
522 + args: args{
523 + d: caddyfile.NewTestDispenser(` {
524 + renegotiation freely
525 + }`),
526 + },
527 + expected: TLSConfig{
528 + Renegotiation: "freely",
529 + },
530 + },
531 + {
532 + name: "setting 'renegotiation' to other than 'none', 'once, or 'freely' is invalid",
533 + args: args{
534 + d: caddyfile.NewTestDispenser(` {
535 + renegotiation foo
536 + }`),
537 + },
538 + wantErr: true,
539 + },
540 + {
541 + name: "setting 'renegotiation' without argument is invalid",
542 + args: args{
543 + d: caddyfile.NewTestDispenser(` {
544 + renegotiation
545 + }`),
546 + },
547 + wantErr: true,
548 + },
549 + {
550 + name: "setting 'ca' without arguemnt is an error",
551 + args: args{
552 + d: caddyfile.NewTestDispenser(`{
553 + ca
554 + }`),
555 + },
556 + wantErr: true,
557 + },
558 + {
559 + name: "setting 'ca' to 'file' with in-line cert is valid",
560 + args: args{
561 + d: caddyfile.NewTestDispenser(`{
562 + ca file /var/caddy/ca.pem
563 + }`),
564 + },
565 + expected: TLSConfig{
566 + CARaw: []byte(`{"pem_files":["/var/caddy/ca.pem"],"provider":"file"}`),
567 + },
568 + },
569 + {
570 + name: "setting 'ca' to 'lazy' with appropriate block is valid",
571 + args: args{
572 + d: caddyfile.NewTestDispenser(`{
573 + ca lazy {
574 + backend file {
575 + pem_file /var/caddy/ca.pem
576 + }
577 + }
578 + }`),
579 + },
580 + expected: TLSConfig{
581 + CARaw: []byte(`{"ca":{"pem_files":["/var/caddy/ca.pem"],"provider":"file"},"provider":"lazy"}`),
582 + },
583 + },
584 + {
585 + name: "setting 'ca' to 'file' with appropriate block is valid",
586 + args: args{
587 + d: caddyfile.NewTestDispenser(`{
588 + ca file /var/caddy/ca.pem {
589 + pem_file /var/caddy/ca.pem
590 + }
591 + }`),
592 + },
593 + expected: TLSConfig{
594 + CARaw: []byte(`{"pem_files":["/var/caddy/ca.pem","/var/caddy/ca.pem"],"provider":"file"}`),
595 + },
596 + },
597 + {
598 + name: "setting 'ca' multiple times is an error",
599 + args: args{
600 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`{
601 + ca file /var/caddy/ca.pem {
602 + pem_file /var/caddy/ca.pem
603 + }
604 + ca inline %s
605 + }`, test_der_1)),
606 + },
607 + wantErr: true,
608 + },
609 + {
610 + name: "setting 'handshake_timeout' without value is an error",
611 + args: args{
612 + d: caddyfile.NewTestDispenser(`{
613 + handshake_timeout
614 + }`),
615 + },
616 + wantErr: true,
617 + },
618 + {
619 + name: "setting 'handshake_timeout' properly is successful",
620 + args: args{
621 + d: caddyfile.NewTestDispenser(`{
622 + handshake_timeout 42m
623 + }`),
624 + },
625 + expected: TLSConfig{
626 + HandshakeTimeout: caddy.Duration(42 * time.Minute),
627 + },
628 + },
629 + {
630 + name: "setting 'server_name' without value is an error",
631 + args: args{
632 + d: caddyfile.NewTestDispenser(`{
633 + server_name
634 + }`),
635 + },
636 + wantErr: true,
637 + },
638 + {
639 + name: "setting 'server_name' properly is successful",
640 + args: args{
641 + d: caddyfile.NewTestDispenser(`{
642 + server_name example.com
643 + }`),
644 + },
645 + expected: TLSConfig{
646 + ServerName: "example.com",
647 + },
648 + },
649 + {
650 + name: "unsupported directives are errors",
651 + args: args{
652 + d: caddyfile.NewTestDispenser(`{
653 + foo
654 + }`),
655 + },
656 + wantErr: true,
657 + },
658 + }
659 + for _, tt := range tests {
660 + t.Run(tt.name, func(t *testing.T) {
661 + tr := &TLSConfig{}
662 + if err := tr.unmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
663 + t.Errorf("TLSConfig.unmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
664 + }
665 + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, tr) {
666 + t.Errorf("TLSConfig.UnmarshalCaddyfile() = %v, want %v", tr, tt.expected)
667 + }
668 + })
669 + }
670 +}
671 +
672 +func TestHTTPCertPoolUnmarshalCaddyfile(t *testing.T) {
673 + type args struct {
674 + d *caddyfile.Dispenser
675 + }
676 + tests := []struct {
677 + name string
678 + args args
679 + expected HTTPCertPool
680 + wantErr bool
681 + }{
682 + {
683 + name: "no block, inline http endpoint",
684 + args: args{
685 + d: caddyfile.NewTestDispenser(`http http://localhost/ca-certs`),
686 + },
687 + expected: HTTPCertPool{
688 + Endpoints: []string{"http://localhost/ca-certs"},
689 + },
690 + wantErr: false,
691 + },
692 + {
693 + name: "no block, inline https endpoint",
694 + args: args{
695 + d: caddyfile.NewTestDispenser(`http https://localhost/ca-certs`),
696 + },
697 + expected: HTTPCertPool{
698 + Endpoints: []string{"https://localhost/ca-certs"},
699 + },
700 + wantErr: false,
701 + },
702 + {
703 + name: "no block, mixed http and https endpoints inline",
704 + args: args{
705 + d: caddyfile.NewTestDispenser(`http http://localhost/ca-certs https://localhost/ca-certs`),
706 + },
707 + expected: HTTPCertPool{
708 + Endpoints: []string{"http://localhost/ca-certs", "https://localhost/ca-certs"},
709 + },
710 + wantErr: false,
711 + },
712 + {
713 + name: "multiple endpoints in separate lines in block",
714 + args: args{
715 + d: caddyfile.NewTestDispenser(`
716 + http {
717 + endpoints http://localhost/ca-certs
718 + endpoints http://remotehost/ca-certs
719 + }
720 + `),
721 + },
722 + expected: HTTPCertPool{
723 + Endpoints: []string{"http://localhost/ca-certs", "http://remotehost/ca-certs"},
724 + },
725 + wantErr: false,
726 + },
727 + {
728 + name: "endpoints defiend inline and in block are merged",
729 + args: args{
730 + d: caddyfile.NewTestDispenser(`http http://localhost/ca-certs {
731 + endpoints http://remotehost/ca-certs
732 + }`),
733 + },
734 + expected: HTTPCertPool{
735 + Endpoints: []string{"http://localhost/ca-certs", "http://remotehost/ca-certs"},
736 + },
737 + wantErr: false,
738 + },
739 + {
740 + name: "multiple endpoints defiend in block on the same line",
741 + args: args{
742 + d: caddyfile.NewTestDispenser(`http {
743 + endpoints http://remotehost/ca-certs http://localhost/ca-certs
744 + }`),
745 + },
746 + expected: HTTPCertPool{
747 + Endpoints: []string{"http://remotehost/ca-certs", "http://localhost/ca-certs"},
748 + },
749 + wantErr: false,
750 + },
751 + {
752 + name: "declaring 'endpoints' in block without argument is an error",
753 + args: args{
754 + d: caddyfile.NewTestDispenser(`http {
755 + endpoints
756 + }`),
757 + },
758 + wantErr: true,
759 + },
760 + {
761 + name: "multiple endpoints in separate lines in block",
762 + args: args{
763 + d: caddyfile.NewTestDispenser(`
764 + http {
765 + endpoints http://localhost/ca-certs
766 + endpoints http://remotehost/ca-certs
767 + tls {
768 + renegotiation freely
769 + }
770 + }
771 + `),
772 + },
773 + expected: HTTPCertPool{
774 + Endpoints: []string{"http://localhost/ca-certs", "http://remotehost/ca-certs"},
775 + TLS: &TLSConfig{
776 + Renegotiation: "freely",
777 + },
778 + },
779 + wantErr: false,
780 + },
781 + }
782 + for _, tt := range tests {
783 + t.Run(tt.name, func(t *testing.T) {
784 + hcp := &HTTPCertPool{}
785 + if err := hcp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
786 + t.Errorf("HTTPCertPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
787 + }
788 + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, hcp) {
789 + t.Errorf("HTTPCertPool.UnmarshalCaddyfile() = %v, want %v", hcp, tt.expected)
790 + }
791 + })
792 + }
793 +}
794 +
795 +func TestLazyCertPoolUnmarshalCaddyfile(t *testing.T) {
796 + type args struct {
797 + d *caddyfile.Dispenser
798 + }
799 + tests := []struct {
800 + name string
801 + args args
802 + expected LazyCertPool
803 + wantErr bool
804 + }{
805 + {
806 + name: "no block results in error",
807 + args: args{
808 + d: caddyfile.NewTestDispenser(`lazy`),
809 + },
810 + wantErr: true,
811 + },
812 + {
813 + name: "empty block results in error",
814 + args: args{
815 + d: caddyfile.NewTestDispenser(`lazy {
816 + }`),
817 + },
818 + wantErr: true,
819 + },
820 + {
821 + name: "defining 'backend' multiple times results in error",
822 + args: args{
823 + d: caddyfile.NewTestDispenser(`lazy {
824 + backend http {
825 + endpoints http://localhost/ca-certs
826 + }
827 + backend file {
828 + pem_file /var/caddy/certs
829 + }
830 + }`),
831 + },
832 + wantErr: true,
833 + },
834 + {
835 + name: "defining 'backend' without argument results in error",
836 + args: args{
837 + d: caddyfile.NewTestDispenser(`lazy {
838 + backend
839 + }`),
840 + },
841 + wantErr: true,
842 + },
843 + {
844 + name: "using unrecognized directive results in error",
845 + args: args{
846 + d: caddyfile.NewTestDispenser(`lazy {
847 + foo
848 + }`),
849 + },
850 + wantErr: true,
851 + },
852 + {
853 + name: "defining single 'backend' is successful",
854 + args: args{
855 + d: caddyfile.NewTestDispenser(`lazy {
856 + backend http {
857 + endpoints http://localhost/ca-certs
858 + }
859 + }`),
860 + },
861 + expected: LazyCertPool{
862 + CARaw: []byte(`{"endpoints":["http://localhost/ca-certs"],"provider":"http"}`),
863 + },
864 + },
865 + {
866 + name: "defining single 'backend' with 'eager_validation' successful",
867 + args: args{
868 + d: caddyfile.NewTestDispenser(`lazy {
869 + backend file {
870 + pem_file /var/caddy/certs
871 + }
872 + eager_validation
873 + }`),
874 + },
875 + expected: LazyCertPool{
876 + CARaw: []byte(`{"pem_files":["/var/caddy/certs"],"provider":"file"}`),
877 + EagerValidation: true,
878 + },
879 + },
880 + }
881 + for _, tt := range tests {
882 + t.Run(tt.name, func(t *testing.T) {
883 + lcp := &LazyCertPool{}
884 + if err := lcp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
885 + t.Errorf("LazyCertPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
886 + }
887 + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, lcp) {
888 + t.Errorf("LazyCertPool.UnmarshalCaddyfile() = %v, want %v", lcp, tt.expected)
889 + }
890 + })
891 + }
892 +}
modules/caddytls/connpolicy.go
+184 -21
@@ -19,6 +19,7 @@ import (
19 "crypto/x509"
20 "encoding/base64"
21 "encoding/json"
22 + "encoding/pem"
23 "fmt"
24 "io"
25 "os"
@@ -29,6 +30,8 @@ import (
30 "go.uber.org/zap"
31
32 "github.com/caddyserver/caddy/v2"
33 + "github.com/caddyserver/caddy/v2/caddyconfig"
34 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
35 )
36
37 func init() {
@@ -301,8 +304,10 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
304
305 // client authentication
306 if p.ClientAuthentication != nil {
304 - err := p.ClientAuthentication.ConfigureTLSConfig(cfg)
305 - if err != nil {
307 + if err := p.ClientAuthentication.provision(ctx); err != nil {
308 + return fmt.Errorf("provisioning client CA: %v", err)
309 + }
310 + if err := p.ClientAuthentication.ConfigureTLSConfig(cfg); err != nil {
311 return fmt.Errorf("configuring TLS client authentication: %v", err)
312 }
313 }
@@ -354,12 +359,18 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
359
360 // ClientAuthentication configures TLS client auth.
361 type ClientAuthentication struct {
362 + // Certificate authority module which provides the certificate pool of trusted certificates
363 + CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
364 + ca CA
365 +
366 + // DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.inline` module instead.
367 // A list of base64 DER-encoded CA certificates
368 // against which to validate client certificates.
369 // Client certs which are not signed by any of
370 // these CAs will be rejected.
371 TrustedCACerts []string `json:"trusted_ca_certs,omitempty"`
372
373 + // DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.file` module instead.
374 // TrustedCACertPEMFiles is a list of PEM file names
375 // from which to load certificates of trusted CAs.
376 // Client certificates which are not signed by any of
@@ -399,13 +410,177 @@ type ClientAuthentication struct {
410 existingVerifyPeerCert func([][]byte, [][]*x509.Certificate) error
411 }
412
413 +// UnmarshalCaddyfile parses the Caddyfile segment to set up the client authentication. Syntax:
414 +//
415 +// client_auth {
416 +// mode [request|require|verify_if_given|require_and_verify]
417 +// trust_pool <module> {
418 +// ...
419 +// }
420 +// trusted_leaf_cert <base64_der>
421 +// trusted_leaf_cert_file <filename>
422 +// }
423 +//
424 +// If `mode` is not provided, it defaults to `require_and_verify` if any of the following are provided:
425 +// - `trusted_leaf_certs`
426 +// - `trusted_leaf_cert_file`
427 +// - `trust_pool`
428 +//
429 +// Otherwise, it defaults to `require`.
430 +func (ca *ClientAuthentication) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
431 + for d.NextArg() {
432 + // consume any tokens on the same line, if any.
433 + }
434 + for nesting := d.Nesting(); d.NextBlock(nesting); {
435 + subdir := d.Val()
436 + switch subdir {
437 + case "mode":
438 + if d.CountRemainingArgs() > 1 {
439 + return d.ArgErr()
440 + }
441 + if !d.Args(&ca.Mode) {
442 + return d.ArgErr()
443 + }
444 + case "trusted_ca_cert":
445 + if len(ca.CARaw) != 0 {
446 + return d.Err("cannot specify both 'trust_pool' and 'trusted_ca_cert' or 'trusted_ca_cert_file'")
447 + }
448 + if !d.NextArg() {
449 + return d.ArgErr()
450 + }
451 + ca.TrustedCACerts = append(ca.TrustedCACerts, d.Val())
452 + case "trusted_leaf_cert":
453 + if !d.NextArg() {
454 + return d.ArgErr()
455 + }
456 + ca.TrustedLeafCerts = append(ca.TrustedLeafCerts, d.Val())
457 + case "trusted_ca_cert_file":
458 + if len(ca.CARaw) != 0 {
459 + return d.Err("cannot specify both 'trust_pool' and 'trusted_ca_cert' or 'trusted_ca_cert_file'")
460 + }
461 + if !d.NextArg() {
462 + return d.ArgErr()
463 + }
464 + filename := d.Val()
465 + ders, err := convertPEMFilesToDER(filename)
466 + if err != nil {
467 + return d.WrapErr(err)
468 + }
469 + ca.TrustedCACerts = append(ca.TrustedCACerts, ders...)
470 + case "trusted_leaf_cert_file":
471 + if !d.NextArg() {
472 + return d.ArgErr()
473 + }
474 + filename := d.Val()
475 + ders, err := convertPEMFilesToDER(filename)
476 + if err != nil {
477 + return d.WrapErr(err)
478 + }
479 + ca.TrustedLeafCerts = append(ca.TrustedLeafCerts, ders...)
480 + case "trust_pool":
481 + if len(ca.TrustedCACerts) != 0 {
482 + return d.Err("cannot specify both 'trust_pool' and 'trusted_ca_cert' or 'trusted_ca_cert_file'")
483 + }
484 + if !d.NextArg() {
485 + return d.ArgErr()
486 + }
487 + modName := d.Val()
488 + mod, err := caddyfile.UnmarshalModule(d, "tls.ca_pool.source."+modName)
489 + if err != nil {
490 + return d.WrapErr(err)
491 + }
492 + caMod, ok := mod.(CA)
493 + if !ok {
494 + return fmt.Errorf("trust_pool module '%s' is not a certificate pool provider", caMod)
495 + }
496 + ca.CARaw = caddyconfig.JSONModuleObject(caMod, "provider", modName, nil)
497 + default:
498 + return d.Errf("unknown subdirective for client_auth: %s", subdir)
499 + }
500 + }
501 +
502 + // only trust_ca_cert or trust_ca_cert_file was specified
503 + if len(ca.TrustedCACerts) > 0 {
504 + fileMod := &InlineCAPool{}
505 + fileMod.TrustedCACerts = append(fileMod.TrustedCACerts, ca.TrustedCACerts...)
506 + ca.CARaw = caddyconfig.JSONModuleObject(fileMod, "provider", "inline", nil)
507 + ca.TrustedCACertPEMFiles, ca.TrustedCACerts = nil, nil
508 + }
509 + return nil
510 +}
511 +
512 +func convertPEMFilesToDER(filename string) ([]string, error) {
513 + certDataPEM, err := os.ReadFile(filename)
514 + if err != nil {
515 + return nil, err
516 + }
517 + var ders []string
518 + // while block is not nil, we have more certificates in the file
519 + for block, rest := pem.Decode(certDataPEM); block != nil; block, rest = pem.Decode(rest) {
520 + if block.Type != "CERTIFICATE" {
521 + return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename)
522 + }
523 + ders = append(
524 + ders,
525 + base64.StdEncoding.EncodeToString(block.Bytes),
526 + )
527 + }
528 + // if we decoded nothing, return an error
529 + if len(ders) == 0 {
530 + return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename)
531 + }
532 + return ders, nil
533 +}
534 +
535 +func (clientauth *ClientAuthentication) provision(ctx caddy.Context) error {
536 + if len(clientauth.CARaw) > 0 && (len(clientauth.TrustedCACerts) > 0 || len(clientauth.TrustedCACertPEMFiles) > 0) {
537 + return fmt.Errorf("conflicting config for client authentication trust CA")
538 + }
539 +
540 + // convert all named file paths to inline
541 + if len(clientauth.TrustedCACertPEMFiles) > 0 {
542 + for _, fpath := range clientauth.TrustedCACertPEMFiles {
543 + ders, err := convertPEMFilesToDER(fpath)
544 + if err != nil {
545 + return nil
546 + }
547 + clientauth.TrustedCACerts = append(clientauth.TrustedCACerts, ders...)
548 + }
549 + }
550 +
551 + // if we have TrustedCACerts explicitly set, create an 'inline' CA and return
552 + if len(clientauth.TrustedCACerts) > 0 {
553 + clientauth.ca = InlineCAPool{
554 + TrustedCACerts: clientauth.TrustedCACerts,
555 + }
556 + return nil
557 + }
558 +
559 + // if we don't have any CARaw set, there's not much work to do
560 + if clientauth.CARaw == nil {
561 + return nil
562 + }
563 + caRaw, err := ctx.LoadModule(clientauth, "CARaw")
564 + if err != nil {
565 + return err
566 + }
567 + ca, ok := caRaw.(CA)
568 + if !ok {
569 + return fmt.Errorf("CARaw module '%s' is not a certificate pool provider", ca)
570 + }
571 + clientauth.ca = ca
572 +
573 + return nil
574 +}
575 +
576 // Active returns true if clientauth has an actionable configuration.
577 func (clientauth ClientAuthentication) Active() bool {
578 return len(clientauth.TrustedCACerts) > 0 ||
579 len(clientauth.TrustedCACertPEMFiles) > 0 ||
580 len(clientauth.TrustedLeafCerts) > 0 || // TODO: DEPRECATED
581 len(clientauth.VerifiersRaw) > 0 ||
408 - len(clientauth.Mode) > 0
582 + len(clientauth.Mode) > 0 ||
583 + clientauth.CARaw != nil || clientauth.ca != nil
584 }
585
586 // ConfigureTLSConfig sets up cfg to enforce clientauth's configuration.
@@ -434,7 +609,8 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
609 // otherwise, set a safe default mode
610 if len(clientauth.TrustedCACerts) > 0 ||
611 len(clientauth.TrustedCACertPEMFiles) > 0 ||
437 - len(clientauth.TrustedLeafCerts) > 0 {
612 + len(clientauth.TrustedLeafCerts) > 0 ||
613 + clientauth.CARaw != nil {
614 cfg.ClientAuth = tls.RequireAndVerifyClientCert
615 } else {
616 cfg.ClientAuth = tls.RequireAnyClientCert
@@ -442,23 +618,8 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
618 }
619
620 // enforce CA verification by adding CA certs to the ClientCAs pool
445 - if len(clientauth.TrustedCACerts) > 0 || len(clientauth.TrustedCACertPEMFiles) > 0 {
446 - caPool := x509.NewCertPool()
447 - for _, clientCAString := range clientauth.TrustedCACerts {
448 - clientCA, err := decodeBase64DERCert(clientCAString)
449 - if err != nil {
450 - return fmt.Errorf("parsing certificate: %v", err)
451 - }
452 - caPool.AddCert(clientCA)
453 - }
454 - for _, pemFile := range clientauth.TrustedCACertPEMFiles {
455 - pemContents, err := os.ReadFile(pemFile)
456 - if err != nil {
457 - return fmt.Errorf("reading %s: %v", pemFile, err)
458 - }
459 - caPool.AppendCertsFromPEM(pemContents)
460 - }
461 - cfg.ClientCAs = caPool
621 + if clientauth.ca != nil {
622 + cfg.ClientCAs = clientauth.ca.CertPool()
623 }
624
625 // TODO: DEPRECATED: Only here for backwards compatibility.
@@ -600,3 +761,5 @@ type destructableWriter struct{ *os.File }
761 func (d destructableWriter) Destruct() error { return d.Close() }
762
763 var secretsLogPool = caddy.NewUsagePool()
764 +
765 +var _ caddyfile.Unmarshaler = (*ClientAuthentication)(nil)
modules/caddytls/connpolicy_test.go new
+280
@@ -0,0 +1,280 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddytls
16 +
17 +import (
18 + "encoding/json"
19 + "fmt"
20 + "reflect"
21 + "testing"
22 +
23 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
24 +)
25 +
26 +func TestClientAuthenticationUnmarshalCaddyfileWithDirectiveName(t *testing.T) {
27 + const test_der_1 = `MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==`
28 + const test_cert_file_1 = "../../caddytest/caddy.ca.cer"
29 + type args struct {
30 + d *caddyfile.Dispenser
31 + }
32 + tests := []struct {
33 + name string
34 + args args
35 + expected ClientAuthentication
36 + wantErr bool
37 + }{
38 + {
39 + name: "empty client_auth block does not error",
40 + args: args{
41 + d: caddyfile.NewTestDispenser(
42 + `client_auth {
43 + }`,
44 + ),
45 + },
46 + wantErr: false,
47 + },
48 + {
49 + name: "providing both 'trust_pool' and 'trusted_ca_cert' returns an error",
50 + args: args{
51 + d: caddyfile.NewTestDispenser(
52 + `client_auth {
53 + trust_pool inline MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
54 + trusted_ca_cert MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
55 + }`),
56 + },
57 + wantErr: true,
58 + },
59 + {
60 + name: "trust_pool without a module argument returns an error",
61 + args: args{
62 + d: caddyfile.NewTestDispenser(
63 + `client_auth {
64 + trust_pool
65 + }`),
66 + },
67 + wantErr: true,
68 + },
69 + {
70 + name: "providing more than 1 mode produces an error",
71 + args: args{
72 + d: caddyfile.NewTestDispenser(`
73 + client_auth {
74 + mode require request
75 + }
76 + `),
77 + },
78 + wantErr: true,
79 + },
80 + {
81 + name: "not providing 'mode' argument produces an error",
82 + args: args{d: caddyfile.NewTestDispenser(`
83 + client_auth {
84 + mode
85 + }
86 + `)},
87 + wantErr: true,
88 + },
89 + {
90 + name: "providing a single 'mode' argument sets the mode",
91 + args: args{
92 + d: caddyfile.NewTestDispenser(`
93 + client_auth {
94 + mode require
95 + }
96 + `),
97 + },
98 + expected: ClientAuthentication{
99 + Mode: "require",
100 + },
101 + wantErr: false,
102 + },
103 + {
104 + name: "not providing an argument to 'trusted_ca_cert' produces an error",
105 + args: args{
106 + d: caddyfile.NewTestDispenser(`
107 + client_auth {
108 + trusted_ca_cert
109 + }
110 + `),
111 + },
112 + wantErr: true,
113 + },
114 + {
115 + name: "not providing an argument to 'trusted_leaf_cert' produces an error",
116 + args: args{
117 + d: caddyfile.NewTestDispenser(`
118 + client_auth {
119 + trusted_leaf_cert
120 + }
121 + `),
122 + },
123 + wantErr: true,
124 + },
125 + {
126 + name: "not providing an argument to 'trusted_ca_cert_file' produces an error",
127 + args: args{
128 + d: caddyfile.NewTestDispenser(`
129 + client_auth {
130 + trusted_ca_cert_file
131 + }
132 + `),
133 + },
134 + wantErr: true,
135 + },
136 + {
137 + name: "not providing an argument to 'trusted_leaf_cert_file' produces an error",
138 + args: args{
139 + d: caddyfile.NewTestDispenser(`
140 + client_auth {
141 + trusted_leaf_cert_file
142 + }
143 + `),
144 + },
145 + wantErr: true,
146 + },
147 + {
148 + name: "using 'trusted_ca_cert' adapts sucessfully",
149 + args: args{
150 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
151 + client_auth {
152 + trusted_ca_cert %s
153 + }`, test_der_1)),
154 + },
155 + expected: ClientAuthentication{
156 + CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1)),
157 + },
158 + },
159 + {
160 + name: "using 'inline' trust_pool loads the module successfully",
161 + args: args{
162 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
163 + client_auth {
164 + trust_pool inline {
165 + trust_der %s
166 + }
167 + }
168 + `, test_der_1)),
169 + },
170 + expected: ClientAuthentication{
171 + CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1)),
172 + },
173 + },
174 + {
175 + name: "setting 'trusted_ca_cert' and 'trust_pool' produces an error",
176 + args: args{
177 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
178 + client_auth {
179 + trusted_ca_cert %s
180 + trust_pool inline {
181 + trust_der %s
182 + }
183 + }`, test_der_1, test_der_1)),
184 + },
185 + wantErr: true,
186 + },
187 + {
188 + name: "setting 'trust_pool' and 'trusted_ca_cert' produces an error",
189 + args: args{
190 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
191 + client_auth {
192 + trust_pool inline {
193 + trust_der %s
194 + }
195 + trusted_ca_cert %s
196 + }`, test_der_1, test_der_1)),
197 + },
198 + wantErr: true,
199 + },
200 + {
201 + name: "setting 'trust_pool' and 'trusted_ca_cert' produces an error",
202 + args: args{
203 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
204 + client_auth {
205 + trust_pool inline {
206 + trust_der %s
207 + }
208 + trusted_ca_cert_file %s
209 + }`, test_der_1, test_cert_file_1)),
210 + },
211 + wantErr: true,
212 + },
213 + {
214 + name: "configuring 'trusted_ca_cert_file' without an argument is an error",
215 + args: args{
216 + d: caddyfile.NewTestDispenser(`
217 + client_auth {
218 + trusted_ca_cert_file
219 + }
220 + `),
221 + },
222 + wantErr: true,
223 + },
224 + {
225 + name: "configuring 'trusted_ca_cert_file' produces config with 'inline' provider",
226 + args: args{
227 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
228 + client_auth {
229 + trusted_ca_cert_file %s
230 + }`, test_cert_file_1),
231 + ),
232 + },
233 + expected: ClientAuthentication{
234 + CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1)),
235 + },
236 + wantErr: false,
237 + },
238 + {
239 + name: "configuring leaf certs does not conflict with 'trust_pool'",
240 + args: args{
241 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
242 + client_auth {
243 + trust_pool inline {
244 + trust_der %s
245 + }
246 + trusted_leaf_cert %s
247 + }`, test_der_1, test_der_1)),
248 + },
249 + expected: ClientAuthentication{
250 + CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1)),
251 + TrustedLeafCerts: []string{test_der_1},
252 + },
253 + },
254 + {
255 + name: "providing trusted leaf certificate file loads the cert successfully",
256 + args: args{
257 + d: caddyfile.NewTestDispenser(fmt.Sprintf(`
258 + client_auth {
259 + trusted_leaf_cert_file %s
260 + }`, test_cert_file_1)),
261 + },
262 + expected: ClientAuthentication{
263 + TrustedLeafCerts: []string{test_der_1},
264 + },
265 + wantErr: false,
266 + },
267 + }
268 + for _, tt := range tests {
269 + t.Run(tt.name, func(t *testing.T) {
270 + ca := &ClientAuthentication{}
271 + if err := ca.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
272 + t.Errorf("ClientAuthentication.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
273 + return
274 + }
275 + if !tt.wantErr && !reflect.DeepEqual(&tt.expected, ca) {
276 + t.Errorf("ClientAuthentication.UnmarshalCaddyfile() = %v, want %v", ca, tt.expected)
277 + }
278 + })
279 + }
280 +}