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

acme_server: Configurable default lifetime for issued certificates (#5232)

* acme_server: add certificate lifetime configuration option Signed-off-by: Kyle McCullough <kylemcc@gmail.com> * pki: allow intermediate cert lifetime to be configured Signed-off-by: Kyle McCullough <kylemcc@gmail.com> Signed-off-by: Kyle McCullough <kylemcc@gmail.com>

Kyle McCullough committed Dec 5, 2022 at 23:12 UTC bfaf2a8201b83d7369772cb6f2439abe66d9342a
8 files changed +268 -8
caddyconfig/httpcaddyfile/pkiapp.go
+15 -3
@@ -15,6 +15,7 @@
15 package httpcaddyfile
16
17 import (
18 + "github.com/caddyserver/caddy/v2"
19 "github.com/caddyserver/caddy/v2/caddyconfig"
20 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
21 "github.com/caddyserver/caddy/v2/modules/caddypki"
@@ -28,9 +29,10 @@ func init() {
29 //
30 // pki {
31 // ca [<id>] {
31 -// name <name>
32 -// root_cn <name>
33 -// intermediate_cn <name>
32 +// name <name>
33 +// root_cn <name>
34 +// intermediate_cn <name>
35 +// intermediate_lifetime <duration>
36 // root {
37 // cert <path>
38 // key <path>
@@ -83,6 +85,16 @@ func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
85 }
86 pkiCa.IntermediateCommonName = d.Val()
87
88 + case "intermediate_lifetime":
89 + if !d.NextArg() {
90 + return nil, d.ArgErr()
91 + }
92 + dur, err := caddy.ParseDuration(d.Val())
93 + if err != nil {
94 + return nil, err
95 + }
96 + pkiCa.IntermediateLifetime = caddy.Duration(dur)
97 +
98 case "root":
99 if pkiCa.Root == nil {
100 pkiCa.Root = new(caddypki.KeyPair)
caddytest/integration/caddyfile_adapt/acme_server_lifetime.txt new
+108
@@ -0,0 +1,108 @@
1 +{
2 + pki {
3 + ca internal {
4 + name "Internal"
5 + root_cn "Internal Root Cert"
6 + intermediate_cn "Internal Intermediate Cert"
7 + }
8 + ca internal-long-lived {
9 + name "Long-lived"
10 + root_cn "Internal Root Cert 2"
11 + intermediate_cn "Internal Intermediate Cert 2"
12 + }
13 + }
14 +}
15 +
16 +acme-internal.example.com {
17 + acme_server {
18 + ca internal
19 + }
20 +}
21 +
22 +acme-long-lived.example.com {
23 + acme_server {
24 + ca internal-long-lived
25 + lifetime 7d
26 + }
27 +}
28 +----------
29 +{
30 + "apps": {
31 + "http": {
32 + "servers": {
33 + "srv0": {
34 + "listen": [
35 + ":443"
36 + ],
37 + "routes": [
38 + {
39 + "match": [
40 + {
41 + "host": [
42 + "acme-long-lived.example.com"
43 + ]
44 + }
45 + ],
46 + "handle": [
47 + {
48 + "handler": "subroute",
49 + "routes": [
50 + {
51 + "handle": [
52 + {
53 + "ca": "internal-long-lived",
54 + "handler": "acme_server",
55 + "lifetime": 604800000000000
56 + }
57 + ]
58 + }
59 + ]
60 + }
61 + ],
62 + "terminal": true
63 + },
64 + {
65 + "match": [
66 + {
67 + "host": [
68 + "acme-internal.example.com"
69 + ]
70 + }
71 + ],
72 + "handle": [
73 + {
74 + "handler": "subroute",
75 + "routes": [
76 + {
77 + "handle": [
78 + {
79 + "ca": "internal",
80 + "handler": "acme_server"
81 + }
82 + ]
83 + }
84 + ]
85 + }
86 + ],
87 + "terminal": true
88 + }
89 + ]
90 + }
91 + }
92 + },
93 + "pki": {
94 + "certificate_authorities": {
95 + "internal": {
96 + "name": "Internal",
97 + "root_common_name": "Internal Root Cert",
98 + "intermediate_common_name": "Internal Intermediate Cert"
99 + },
100 + "internal-long-lived": {
101 + "name": "Long-lived",
102 + "root_common_name": "Internal Root Cert 2",
103 + "intermediate_common_name": "Internal Intermediate Cert 2"
104 + }
105 + }
106 + }
107 + }
108 +}
caddytest/integration/caddyfile_adapt/global_options_skip_install_trust.txt
+1 -1
@@ -165,4 +165,4 @@ acme-bar.example.com {
165 }
166 }
167 }
168 -}
\ No newline at end of file
168 +}
caddytest/integration/pki_test.go new
+101
@@ -0,0 +1,101 @@
1 +package integration
2 +
3 +import (
4 + "testing"
5 +
6 + "github.com/caddyserver/caddy/v2/caddytest"
7 +)
8 +
9 +func TestLeafCertLifetimeLessThanIntermediate(t *testing.T) {
10 + caddytest.AssertLoadError(t, `
11 + {
12 + "apps": {
13 + "http": {
14 + "servers": {
15 + "srv0": {
16 + "listen": [
17 + ":443"
18 + ],
19 + "routes": [
20 + {
21 + "handle": [
22 + {
23 + "handler": "subroute",
24 + "routes": [
25 + {
26 + "handle": [
27 + {
28 + "ca": "internal",
29 + "handler": "acme_server",
30 + "lifetime": 604800000000000
31 + }
32 + ]
33 + }
34 + ]
35 + }
36 + ]
37 + }
38 + ]
39 + }
40 + }
41 + },
42 + "pki": {
43 + "certificate_authorities": {
44 + "internal": {
45 + "install_trust": false,
46 + "intermediate_lifetime": 604800000000000,
47 + "name": "Internal CA"
48 + }
49 + }
50 + }
51 + }
52 + }
53 + `, "json", "certificate lifetime (168h0m0s) should be less than intermediate certificate lifetime (168h0m0s)")
54 +}
55 +
56 +func TestIntermediateLifetimeLessThanRoot(t *testing.T) {
57 + caddytest.AssertLoadError(t, `
58 + {
59 + "apps": {
60 + "http": {
61 + "servers": {
62 + "srv0": {
63 + "listen": [
64 + ":443"
65 + ],
66 + "routes": [
67 + {
68 + "handle": [
69 + {
70 + "handler": "subroute",
71 + "routes": [
72 + {
73 + "handle": [
74 + {
75 + "ca": "internal",
76 + "handler": "acme_server",
77 + "lifetime": 2592000000000000
78 + }
79 + ]
80 + }
81 + ]
82 + }
83 + ]
84 + }
85 + ]
86 + }
87 + }
88 + },
89 + "pki": {
90 + "certificate_authorities": {
91 + "internal": {
92 + "install_trust": false,
93 + "intermediate_lifetime": 311040000000000000,
94 + "name": "Internal CA"
95 + }
96 + }
97 + }
98 + }
99 + }
100 + `, "json", "intermediate certificate lifetime must be less than root certificate lifetime (86400h0m0s)")
101 +}
modules/caddypki/acmeserver/acmeserver.go
+13 -1
@@ -48,6 +48,9 @@ type Handler struct {
48 // the default ID is "local".
49 CA string `json:"ca,omitempty"`
50
51 + // The lifetime for issued certificates
52 + Lifetime caddy.Duration `json:"lifetime,omitempty"`
53 +
54 // The hostname or IP address by which ACME clients
55 // will access the server. This is used to populate
56 // the ACME directory endpoint. If not set, the Host
@@ -95,6 +98,9 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
98 if ash.PathPrefix == "" {
99 ash.PathPrefix = defaultPathPrefix
100 }
101 + if ash.Lifetime == 0 {
102 + ash.Lifetime = caddy.Duration(12 * time.Hour)
103 + }
104
105 // get a reference to the configured CA
106 appModule, err := ctx.App("pki")
@@ -107,6 +113,12 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
113 return err
114 }
115
116 + // make sure leaf cert lifetime is less than the intermediate cert lifetime. this check only
117 + // applies for caddy-managed intermediate certificates
118 + if ca.Intermediate == nil && ash.Lifetime >= ca.IntermediateLifetime {
119 + return fmt.Errorf("certificate lifetime (%s) should be less than intermediate certificate lifetime (%s)", time.Duration(ash.Lifetime), time.Duration(ca.IntermediateLifetime))
120 + }
121 +
122 database, err := ash.openDatabase()
123 if err != nil {
124 return err
@@ -122,7 +134,7 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
134 Claims: &provisioner.Claims{
135 MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
136 MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour * 365},
125 - DefaultTLSDur: &provisioner.Duration{Duration: 12 * time.Hour},
137 + DefaultTLSDur: &provisioner.Duration{Duration: time.Duration(ash.Lifetime)},
138 },
139 },
140 },
modules/caddypki/acmeserver/caddyfile.go
+19
@@ -15,6 +15,9 @@
15 package acmeserver
16
17 import (
18 + "time"
19 +
20 + "github.com/caddyserver/caddy/v2"
21 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
22 "github.com/caddyserver/caddy/v2/modules/caddypki"
23 )
@@ -27,6 +30,7 @@ func init() {
30 //
31 // acme_server [<matcher>] {
32 // ca <id>
33 +// lifetime <duration>
34 // }
35 func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
36 if !h.Next() {
@@ -55,6 +59,21 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
59 ca = new(caddypki.CA)
60 }
61 ca.ID = acmeServer.CA
62 + case "lifetime":
63 + if !h.NextArg() {
64 + return nil, h.ArgErr()
65 + }
66 +
67 + dur, err := caddy.ParseDuration(h.Val())
68 + if err != nil {
69 + return nil, err
70 + }
71 +
72 + if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d {
73 + return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d)
74 + }
75 +
76 + acmeServer.Lifetime = caddy.Duration(dur)
77 }
78 }
79 }
modules/caddypki/ca.go
+9 -1
@@ -48,6 +48,9 @@ type CA struct {
48 // intermediate certificates.
49 IntermediateCommonName string `json:"intermediate_common_name,omitempty"`
50
51 + // The lifetime for the intermediate certificates
52 + IntermediateLifetime caddy.Duration `json:"intermediate_lifetime,omitempty"`
53 +
54 // Whether Caddy will attempt to install the CA's root
55 // into the system trust store, as well as into Java
56 // and Mozilla Firefox trust stores. Default: true.
@@ -118,6 +121,11 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
121 if ca.IntermediateCommonName == "" {
122 ca.IntermediateCommonName = defaultIntermediateCommonName
123 }
124 + if ca.IntermediateLifetime == 0 {
125 + ca.IntermediateLifetime = caddy.Duration(defaultIntermediateLifetime)
126 + } else if time.Duration(ca.IntermediateLifetime) >= defaultRootLifetime {
127 + return fmt.Errorf("intermediate certificate lifetime must be less than root certificate lifetime (%s)", defaultRootLifetime)
128 + }
129
130 // load the certs and key that will be used for signing
131 var rootCert, interCert *x509.Certificate
@@ -341,7 +349,7 @@ func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Si
349 func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) {
350 repl := ca.newReplacer()
351
344 - interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey)
352 + interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey, time.Duration(ca.IntermediateLifetime))
353 if err != nil {
354 return nil, nil, fmt.Errorf("generating CA intermediate: %v", err)
355 }
modules/caddypki/certificates.go
+2 -2
@@ -35,8 +35,8 @@ func generateRoot(commonName string) (*x509.Certificate, crypto.Signer, error) {
35 return root, signer, nil
36 }
37
38 -func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey crypto.Signer) (*x509.Certificate, crypto.Signer, error) {
39 - template, signer, err := newCert(commonName, x509util.DefaultIntermediateTemplate, defaultIntermediateLifetime)
38 +func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey crypto.Signer, lifetime time.Duration) (*x509.Certificate, crypto.Signer, error) {
39 + template, signer, err := newCert(commonName, x509util.DefaultIntermediateTemplate, lifetime)
40 if err != nil {
41 return nil, nil, err
42 }