mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
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>
This commit is contained in:
parent
fef9cb3e05
commit
bfaf2a8201
8 changed files with 268 additions and 8 deletions
|
@ -15,6 +15,7 @@
|
|||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
|
@ -28,9 +29,10 @@ func init() {
|
|||
//
|
||||
// pki {
|
||||
// ca [<id>] {
|
||||
// name <name>
|
||||
// root_cn <name>
|
||||
// intermediate_cn <name>
|
||||
// name <name>
|
||||
// root_cn <name>
|
||||
// intermediate_cn <name>
|
||||
// intermediate_lifetime <duration>
|
||||
// root {
|
||||
// cert <path>
|
||||
// key <path>
|
||||
|
@ -83,6 +85,16 @@ func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
|||
}
|
||||
pkiCa.IntermediateCommonName = d.Val()
|
||||
|
||||
case "intermediate_lifetime":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
dur, err := caddy.ParseDuration(d.Val())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkiCa.IntermediateLifetime = caddy.Duration(dur)
|
||||
|
||||
case "root":
|
||||
if pkiCa.Root == nil {
|
||||
pkiCa.Root = new(caddypki.KeyPair)
|
||||
|
|
108
caddytest/integration/caddyfile_adapt/acme_server_lifetime.txt
Normal file
108
caddytest/integration/caddyfile_adapt/acme_server_lifetime.txt
Normal file
|
@ -0,0 +1,108 @@
|
|||
{
|
||||
pki {
|
||||
ca internal {
|
||||
name "Internal"
|
||||
root_cn "Internal Root Cert"
|
||||
intermediate_cn "Internal Intermediate Cert"
|
||||
}
|
||||
ca internal-long-lived {
|
||||
name "Long-lived"
|
||||
root_cn "Internal Root Cert 2"
|
||||
intermediate_cn "Internal Intermediate Cert 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
acme-internal.example.com {
|
||||
acme_server {
|
||||
ca internal
|
||||
}
|
||||
}
|
||||
|
||||
acme-long-lived.example.com {
|
||||
acme_server {
|
||||
ca internal-long-lived
|
||||
lifetime 7d
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"acme-long-lived.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"ca": "internal-long-lived",
|
||||
"handler": "acme_server",
|
||||
"lifetime": 604800000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"acme-internal.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"ca": "internal",
|
||||
"handler": "acme_server"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"internal": {
|
||||
"name": "Internal",
|
||||
"root_common_name": "Internal Root Cert",
|
||||
"intermediate_common_name": "Internal Intermediate Cert"
|
||||
},
|
||||
"internal-long-lived": {
|
||||
"name": "Long-lived",
|
||||
"root_common_name": "Internal Root Cert 2",
|
||||
"intermediate_common_name": "Internal Intermediate Cert 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -165,4 +165,4 @@ acme-bar.example.com {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
101
caddytest/integration/pki_test.go
Normal file
101
caddytest/integration/pki_test.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestLeafCertLifetimeLessThanIntermediate(t *testing.T) {
|
||||
caddytest.AssertLoadError(t, `
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"ca": "internal",
|
||||
"handler": "acme_server",
|
||||
"lifetime": 604800000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"internal": {
|
||||
"install_trust": false,
|
||||
"intermediate_lifetime": 604800000000000,
|
||||
"name": "Internal CA"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json", "certificate lifetime (168h0m0s) should be less than intermediate certificate lifetime (168h0m0s)")
|
||||
}
|
||||
|
||||
func TestIntermediateLifetimeLessThanRoot(t *testing.T) {
|
||||
caddytest.AssertLoadError(t, `
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"ca": "internal",
|
||||
"handler": "acme_server",
|
||||
"lifetime": 2592000000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"internal": {
|
||||
"install_trust": false,
|
||||
"intermediate_lifetime": 311040000000000000,
|
||||
"name": "Internal CA"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json", "intermediate certificate lifetime must be less than root certificate lifetime (86400h0m0s)")
|
||||
}
|
|
@ -48,6 +48,9 @@ type Handler struct {
|
|||
// the default ID is "local".
|
||||
CA string `json:"ca,omitempty"`
|
||||
|
||||
// The lifetime for issued certificates
|
||||
Lifetime caddy.Duration `json:"lifetime,omitempty"`
|
||||
|
||||
// The hostname or IP address by which ACME clients
|
||||
// will access the server. This is used to populate
|
||||
// the ACME directory endpoint. If not set, the Host
|
||||
|
@ -95,6 +98,9 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
|
|||
if ash.PathPrefix == "" {
|
||||
ash.PathPrefix = defaultPathPrefix
|
||||
}
|
||||
if ash.Lifetime == 0 {
|
||||
ash.Lifetime = caddy.Duration(12 * time.Hour)
|
||||
}
|
||||
|
||||
// get a reference to the configured CA
|
||||
appModule, err := ctx.App("pki")
|
||||
|
@ -107,6 +113,12 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// make sure leaf cert lifetime is less than the intermediate cert lifetime. this check only
|
||||
// applies for caddy-managed intermediate certificates
|
||||
if ca.Intermediate == nil && ash.Lifetime >= ca.IntermediateLifetime {
|
||||
return fmt.Errorf("certificate lifetime (%s) should be less than intermediate certificate lifetime (%s)", time.Duration(ash.Lifetime), time.Duration(ca.IntermediateLifetime))
|
||||
}
|
||||
|
||||
database, err := ash.openDatabase()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -122,7 +134,7 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
|
|||
Claims: &provisioner.Claims{
|
||||
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
|
||||
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour * 365},
|
||||
DefaultTLSDur: &provisioner.Duration{Duration: 12 * time.Hour},
|
||||
DefaultTLSDur: &provisioner.Duration{Duration: time.Duration(ash.Lifetime)},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
package acmeserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
)
|
||||
|
@ -27,6 +30,7 @@ func init() {
|
|||
//
|
||||
// acme_server [<matcher>] {
|
||||
// ca <id>
|
||||
// lifetime <duration>
|
||||
// }
|
||||
func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
|
||||
if !h.Next() {
|
||||
|
@ -55,6 +59,21 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
|
|||
ca = new(caddypki.CA)
|
||||
}
|
||||
ca.ID = acmeServer.CA
|
||||
case "lifetime":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
dur, err := caddy.ParseDuration(h.Val())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d {
|
||||
return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d)
|
||||
}
|
||||
|
||||
acmeServer.Lifetime = caddy.Duration(dur)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,9 @@ type CA struct {
|
|||
// intermediate certificates.
|
||||
IntermediateCommonName string `json:"intermediate_common_name,omitempty"`
|
||||
|
||||
// The lifetime for the intermediate certificates
|
||||
IntermediateLifetime caddy.Duration `json:"intermediate_lifetime,omitempty"`
|
||||
|
||||
// Whether Caddy will attempt to install the CA's root
|
||||
// into the system trust store, as well as into Java
|
||||
// and Mozilla Firefox trust stores. Default: true.
|
||||
|
@ -118,6 +121,11 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
|
|||
if ca.IntermediateCommonName == "" {
|
||||
ca.IntermediateCommonName = defaultIntermediateCommonName
|
||||
}
|
||||
if ca.IntermediateLifetime == 0 {
|
||||
ca.IntermediateLifetime = caddy.Duration(defaultIntermediateLifetime)
|
||||
} else if time.Duration(ca.IntermediateLifetime) >= defaultRootLifetime {
|
||||
return fmt.Errorf("intermediate certificate lifetime must be less than root certificate lifetime (%s)", defaultRootLifetime)
|
||||
}
|
||||
|
||||
// load the certs and key that will be used for signing
|
||||
var rootCert, interCert *x509.Certificate
|
||||
|
@ -341,7 +349,7 @@ func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Si
|
|||
func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) {
|
||||
repl := ca.newReplacer()
|
||||
|
||||
interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey)
|
||||
interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey, time.Duration(ca.IntermediateLifetime))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("generating CA intermediate: %v", err)
|
||||
}
|
||||
|
|
|
@ -35,8 +35,8 @@ func generateRoot(commonName string) (*x509.Certificate, crypto.Signer, error) {
|
|||
return root, signer, nil
|
||||
}
|
||||
|
||||
func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey crypto.Signer) (*x509.Certificate, crypto.Signer, error) {
|
||||
template, signer, err := newCert(commonName, x509util.DefaultIntermediateTemplate, defaultIntermediateLifetime)
|
||||
func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey crypto.Signer, lifetime time.Duration) (*x509.Certificate, crypto.Signer, error) {
|
||||
template, signer, err := newCert(commonName, x509util.DefaultIntermediateTemplate, lifetime)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue