mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
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.
This commit is contained in:
parent
8ec90f1c40
commit
bf50d7010a
7 changed files with 111 additions and 21 deletions
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -219,6 +220,12 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// then make the PKI app
|
||||||
|
pkiApp, warnings, err := st.buildPKIApp(pairings, options, warnings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, warnings, err
|
||||||
|
}
|
||||||
|
|
||||||
// extract any custom logs, and enforce configured levels
|
// extract any custom logs, and enforce configured levels
|
||||||
var customLogs []namedCustomLog
|
var customLogs []namedCustomLog
|
||||||
var hasDefaultLog bool
|
var hasDefaultLog bool
|
||||||
|
@ -259,6 +266,9 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||||
if !reflect.DeepEqual(tlsApp, &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}) {
|
if !reflect.DeepEqual(tlsApp, &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}) {
|
||||||
cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings)
|
cfg.AppsRaw["tls"] = caddyconfig.JSON(tlsApp, &warnings)
|
||||||
}
|
}
|
||||||
|
if !reflect.DeepEqual(pkiApp, &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}) {
|
||||||
|
cfg.AppsRaw["pki"] = caddyconfig.JSON(pkiApp, &warnings)
|
||||||
|
}
|
||||||
if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok {
|
if storageCvtr, ok := options["storage"].(caddy.StorageConverter); ok {
|
||||||
cfg.StorageRaw = caddyconfig.JSONModuleObject(storageCvtr,
|
cfg.StorageRaw = caddyconfig.JSONModuleObject(storageCvtr,
|
||||||
"module",
|
"module",
|
||||||
|
|
41
caddyconfig/httpcaddyfile/pkiapp.go
Normal file
41
caddyconfig/httpcaddyfile/pkiapp.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package httpcaddyfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
|
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (st ServerType) buildPKIApp(
|
||||||
|
pairings []sbAddrAssociation,
|
||||||
|
options map[string]interface{},
|
||||||
|
warnings []caddyconfig.Warning,
|
||||||
|
) (*caddypki.PKI, []caddyconfig.Warning, error) {
|
||||||
|
|
||||||
|
pkiApp := &caddypki.PKI{CAs: make(map[string]*caddypki.CA)}
|
||||||
|
|
||||||
|
for _, p := range pairings {
|
||||||
|
for _, sblock := range p.serverBlocks {
|
||||||
|
// find all the CAs that were defined and add them to the app config
|
||||||
|
for _, caCfgValue := range sblock.pile["pki.ca"] {
|
||||||
|
ca := caCfgValue.Value.(*caddypki.CA)
|
||||||
|
pkiApp.CAs[ca.ID] = ca
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkiApp, warnings, nil
|
||||||
|
}
|
|
@ -16,23 +16,58 @@ package acmeserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
httpcaddyfile.RegisterHandlerDirective("acme_server", parseACMEServer)
|
httpcaddyfile.RegisterDirective("acme_server", parseACMEServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseACMEServer sets up an ACME server handler from Caddyfile tokens.
|
// parseACMEServer sets up an ACME server handler from Caddyfile tokens.
|
||||||
//
|
//
|
||||||
// acme_server [<matcher>]
|
// acme_server [<matcher>] {
|
||||||
|
// ca <id>
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
func parseACMEServer(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
|
||||||
var as Handler
|
if !h.Next() {
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
matcherSet, err := h.ExtractMatcherSet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var acmeServer Handler
|
||||||
|
var ca *caddypki.CA
|
||||||
|
|
||||||
for h.Next() {
|
for h.Next() {
|
||||||
if h.NextArg() {
|
if h.NextArg() {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
for h.NextBlock(0) {
|
||||||
|
switch h.Val() {
|
||||||
|
case "ca":
|
||||||
|
if !h.AllArgs(&acmeServer.CA) {
|
||||||
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
return as, nil
|
if ca == nil {
|
||||||
|
ca = new(caddypki.CA)
|
||||||
|
}
|
||||||
|
ca.ID = acmeServer.CA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configVals := h.NewRoute(matcherSet, acmeServer)
|
||||||
|
|
||||||
|
if ca == nil {
|
||||||
|
return configVals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(configVals, httpcaddyfile.ConfigValue{
|
||||||
|
Class: "pki.ca",
|
||||||
|
Value: ca,
|
||||||
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,12 @@ type CA struct {
|
||||||
// separate location from your leaf certificates.
|
// separate location from your leaf certificates.
|
||||||
StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
|
StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
|
||||||
|
|
||||||
id string
|
// The unique config-facing ID of the certificate authority.
|
||||||
|
// Since the ID is set in JSON config via object key, this
|
||||||
|
// field is exported only for purposes of config generation
|
||||||
|
// and module provisioning.
|
||||||
|
ID string `json:"-"`
|
||||||
|
|
||||||
storage certmagic.Storage
|
storage certmagic.Storage
|
||||||
root, inter *x509.Certificate
|
root, inter *x509.Certificate
|
||||||
interKey interface{} // TODO: should we just store these as crypto.Signer?
|
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 {
|
||||||
return fmt.Errorf("CA ID is required (use 'local' for the default CA)")
|
return fmt.Errorf("CA ID is required (use 'local' for the default CA)")
|
||||||
}
|
}
|
||||||
ca.mu.Lock()
|
ca.mu.Lock()
|
||||||
ca.id = id
|
ca.ID = id
|
||||||
ca.mu.Unlock()
|
ca.mu.Unlock()
|
||||||
|
|
||||||
if ca.StorageRaw != nil {
|
if ca.StorageRaw != nil {
|
||||||
|
@ -142,11 +147,6 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID returns the CA's ID, as given by the user in the config.
|
|
||||||
func (ca CA) ID() string {
|
|
||||||
return ca.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// RootCertificate returns the CA's root certificate (public key).
|
// RootCertificate returns the CA's root certificate (public key).
|
||||||
func (ca CA) RootCertificate() *x509.Certificate {
|
func (ca CA) RootCertificate() *x509.Certificate {
|
||||||
ca.mu.RLock()
|
ca.mu.RLock()
|
||||||
|
@ -338,7 +338,7 @@ func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey interface{}) (i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ca CA) storageKeyCAPrefix() string {
|
func (ca CA) storageKeyCAPrefix() string {
|
||||||
return path.Join("pki", "authorities", certmagic.StorageKeys.Safe(ca.id))
|
return path.Join("pki", "authorities", certmagic.StorageKeys.Safe(ca.ID))
|
||||||
}
|
}
|
||||||
func (ca CA) storageKeyRootCert() string {
|
func (ca CA) storageKeyRootCert() string {
|
||||||
return path.Join(ca.storageKeyCAPrefix(), "root.crt")
|
return path.Join(ca.storageKeyCAPrefix(), "root.crt")
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (p *PKI) renewCerts() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Error("renewing intermediate certificates",
|
p.log.Error("renewing intermediate certificates",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.String("ca", ca.id))
|
zap.String("ca", ca.ID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ func (p *PKI) renewCertsForCA(ca *CA) error {
|
||||||
ca.mu.Lock()
|
ca.mu.Lock()
|
||||||
defer ca.mu.Unlock()
|
defer ca.mu.Unlock()
|
||||||
|
|
||||||
log := p.log.With(zap.String("ca", ca.id))
|
log := p.log.With(zap.String("ca", ca.ID))
|
||||||
|
|
||||||
// only maintain the root if it's not manually provided in the config
|
// only maintain the root if it's not manually provided in the config
|
||||||
if ca.Root == nil {
|
if ca.Root == nil {
|
||||||
|
|
|
@ -49,10 +49,14 @@ func (p *PKI) Provision(ctx caddy.Context) error {
|
||||||
p.ctx = ctx
|
p.ctx = ctx
|
||||||
p.log = ctx.Logger(p)
|
p.log = ctx.Logger(p)
|
||||||
|
|
||||||
// if this app is initialized at all, ensure there's
|
// if this app is initialized at all, ensure there's at
|
||||||
// at least a default CA that can be used
|
// least a default CA that can be used: the standard CA
|
||||||
if len(p.CAs) == 0 {
|
// which is used implicitly for signing local-use certs
|
||||||
p.CAs = map[string]*CA{DefaultCAID: new(CA)}
|
if p.CAs == nil {
|
||||||
|
p.CAs = make(map[string]*CA)
|
||||||
|
}
|
||||||
|
if _, ok := p.CAs[DefaultCAID]; !ok {
|
||||||
|
p.CAs[DefaultCAID] = new(CA)
|
||||||
}
|
}
|
||||||
|
|
||||||
for caID, ca := range p.CAs {
|
for caID, ca := range p.CAs {
|
||||||
|
|
|
@ -94,7 +94,7 @@ func (iss *InternalIssuer) Provision(ctx caddy.Context) error {
|
||||||
// IssuerKey returns the unique issuer key for the
|
// IssuerKey returns the unique issuer key for the
|
||||||
// confgured CA endpoint.
|
// confgured CA endpoint.
|
||||||
func (iss InternalIssuer) IssuerKey() string {
|
func (iss InternalIssuer) IssuerKey() string {
|
||||||
return iss.ca.ID()
|
return iss.ca.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue issues a certificate to satisfy the CSR.
|
// Issue issues a certificate to satisfy the CSR.
|
||||||
|
|
Loading…
Reference in a new issue