Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
acmeserver: Support custom CAs from Caddyfile
The HTTP Caddyfile adapter can now configure the PKI app, and the acme_server directive can now be used to specify a custom CA used for issuing certificates. More customization options can follow later as needed.
Matthew Holt committed
Feb 2, 2021 at 17:23 UTC
bf50d7010a26468791f4397c0f0c4f9a8ed1d6a2
7 files changed
+111
-21
caddyconfig/httpcaddyfile/httptype.go
+10
@@ -28,6 +28,7 @@ import (
28
"github.com/caddyserver/caddy/v2/caddyconfig"
29
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
30
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
31
+ "github.com/caddyserver/caddy/v2/modules/caddypki"
32
"github.com/caddyserver/caddy/v2/modules/caddytls"
33
)
34
@@ -219,6 +220,12 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
220
return nil, warnings, err
221
}
222
223
+ // then make the PKI app
224
+ pkiApp, warnings, err := st.buildPKIApp(pairings, options, warnings)
225
+ if err != nil {
226
+ return nil, warnings, err
227
+ }
228
+
229
// extract any custom logs, and enforce configured levels
230
var customLogs []namedCustomLog
231
var hasDefaultLog bool
@@ -259,6 +266,9 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
266
if !reflect.DeepEqual(tlsApp, &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}) {
267
cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings)
268
}
269
+ if !reflect.DeepEqual(pkiApp, &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}) {
270
+ cfg.AppsRaw["pki"] = caddyconfig.JSON(pkiApp, &warnings)
271
+ }
272
if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok {
273
cfg.StorageRaw = caddyconfig.JSONModuleObject(storageCvtr,
274
"module",
caddyconfig/httpcaddyfile/pkiapp.go
new
+41
@@ -0,0 +1,41 @@
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 httpcaddyfile
16
+
17
+import (
18
+ "github.com/caddyserver/caddy/v2/caddyconfig"
19
+ "github.com/caddyserver/caddy/v2/modules/caddypki"
20
+)
21
+
22
+func (st ServerType) buildPKIApp(
23
+ pairings []sbAddrAssociation,
24
+ options map[string]interface{},
25
+ warnings []caddyconfig.Warning,
26
+) (*caddypki.PKI, []caddyconfig.Warning, error) {
27
+
28
+ pkiApp := &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}
29
+
30
+ for _, p := range pairings {
31
+ for _, sblock := range p.serverBlocks {
32
+ // find all the CAs that were defined and add them to the app config
33
+ for _, caCfgValue := range sblock.pile["pki.ca"] {
34
+ ca := caCfgValue.Value.(*caddypki.CA)
35
+ pkiApp.CAs[ca.ID] = ca
36
+ }
37
+ }
38
+ }
39
+
40
+ return pkiApp, warnings, nil
41
+}
modules/caddypki/acmeserver/caddyfile.go
+41
-6
@@ -16,23 +16,58 @@ package acmeserver
16
17
import (
18
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
19
- "github.com/caddyserver/caddy/v2/modules/caddyhttp"
19
+ "github.com/caddyserver/caddy/v2/modules/caddypki"
20
)
21
22
func init() {
23
- httpcaddyfile.RegisterHandlerDirective("acme_server", parseACMEServer)
23
+ httpcaddyfile.RegisterDirective("acme_server", parseACMEServer)
24
}
25
26
// parseACMEServer sets up an ACME server handler from Caddyfile tokens.
27
//
28
-// acme_server [<matcher>]
28
+// acme_server [<matcher>] {
29
+// ca <id>
30
+// }
31
//
30
-func parseACMEServer(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
31
- var as Handler
32
+func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
33
+ if !h.Next() {
34
+ return nil, h.ArgErr()
35
+ }
36
+
37
+ matcherSet, err := h.ExtractMatcherSet()
38
+ if err != nil {
39
+ return nil, err
40
+ }
41
+
42
+ var acmeServer Handler
43
+ var ca *caddypki.CA
44
+
45
for h.Next() {
46
if h.NextArg() {
47
return nil, h.ArgErr()
48
}
49
+ for h.NextBlock(0) {
50
+ switch h.Val() {
51
+ case "ca":
52
+ if !h.AllArgs(&acmeServer.CA) {
53
+ return nil, h.ArgErr()
54
+ }
55
+ if ca == nil {
56
+ ca = new(caddypki.CA)
57
+ }
58
+ ca.ID = acmeServer.CA
59
+ }
60
+ }
61
}
37
- return as, nil
62
+
63
+ configVals := h.NewRoute(matcherSet, acmeServer)
64
+
65
+ if ca == nil {
66
+ return configVals, nil
67
+ }
68
+
69
+ return append(configVals, httpcaddyfile.ConfigValue{
70
+ Class: "pki.ca",
71
+ Value: ca,
72
+ }), nil
73
}
modules/caddypki/ca.go
+8
-8
@@ -63,7 +63,12 @@ type CA struct {
63
// separate location from your leaf certificates.
64
StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
65
66
- id string
66
+ // The unique config-facing ID of the certificate authority.
67
+ // Since the ID is set in JSON config via object key, this
68
+ // field is exported only for purposes of config generation
69
+ // and module provisioning.
70
+ ID string `json:"-"`
71
+
72
storage certmagic.Storage
73
root, inter *x509.Certificate
74
interKey interface{} // TODO: should we just store these as crypto.Signer?
@@ -82,7 +87,7 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
87
return fmt.Errorf("CA ID is required (use 'local' for the default CA)")
88
}
89
ca.mu.Lock()
85
- ca.id = id
90
+ ca.ID = id
91
ca.mu.Unlock()
92
93
if ca.StorageRaw != nil {
@@ -142,11 +147,6 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
147
return nil
148
}
149
145
-// ID returns the CA's ID, as given by the user in the config.
146
-func (ca CA) ID() string {
147
- return ca.id
148
-}
149
-
150
// RootCertificate returns the CA's root certificate (public key).
151
func (ca CA) RootCertificate() *x509.Certificate {
152
ca.mu.RLock()
@@ -338,7 +338,7 @@ func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey interface{}) (i
338
}
339
340
func (ca CA) storageKeyCAPrefix() string {
341
- return path.Join("pki", "authorities", certmagic.StorageKeys.Safe(ca.id))
341
+ return path.Join("pki", "authorities", certmagic.StorageKeys.Safe(ca.ID))
342
}
343
func (ca CA) storageKeyRootCert() string {
344
return path.Join(ca.storageKeyCAPrefix(), "root.crt")
modules/caddypki/maintain.go
+2
-2
@@ -50,7 +50,7 @@ func (p *PKI) renewCerts() {
50
if err != nil {
51
p.log.Error("renewing intermediate certificates",
52
zap.Error(err),
53
- zap.String("ca", ca.id))
53
+ zap.String("ca", ca.ID))
54
}
55
}
56
}
@@ -59,7 +59,7 @@ func (p *PKI) renewCertsForCA(ca *CA) error {
59
ca.mu.Lock()
60
defer ca.mu.Unlock()
61
62
- log := p.log.With(zap.String("ca", ca.id))
62
+ log := p.log.With(zap.String("ca", ca.ID))
63
64
// only maintain the root if it's not manually provided in the config
65
if ca.Root == nil {
modules/caddypki/pki.go
+8
-4
@@ -49,10 +49,14 @@ func (p *PKI) Provision(ctx caddy.Context) error {
49
p.ctx = ctx
50
p.log = ctx.Logger(p)
51
52
- // if this app is initialized at all, ensure there's
53
- // at least a default CA that can be used
54
- if len(p.CAs) == 0 {
55
- p.CAs = map[string]*CA{DefaultCAID: new(CA)}
52
+ // if this app is initialized at all, ensure there's at
53
+ // least a default CA that can be used: the standard CA
54
+ // which is used implicitly for signing local-use certs
55
+ if p.CAs == nil {
56
+ p.CAs = make(map[string]*CA)
57
+ }
58
+ if _, ok := p.CAs[DefaultCAID]; !ok {
59
+ p.CAs[DefaultCAID] = new(CA)
60
}
61
62
for caID, ca := range p.CAs {
modules/caddytls/internalissuer.go
+1
-1
@@ -94,7 +94,7 @@ func (iss *InternalIssuer) Provision(ctx caddy.Context) error {
94
// IssuerKey returns the unique issuer key for the
95
// confgured CA endpoint.
96
func (iss InternalIssuer) IssuerKey() string {
97
- return iss.ca.ID()
97
+ return iss.ca.ID
98
}
99
100
// Issue issues a certificate to satisfy the CSR.