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
}