mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-16 21:56:40 -05:00
caddytls: clientauth: leaf verifier: make trusted leaf certs source pluggable (#6050)
* Made trusted leaf certificates pluggable into the tls.client_auth.leaf module * Added leaf loaders modules: file, folder, pem aand storage * Cleaned implementation of leaf cert loader modules * Added tests for leaf certs file and folder loaders * cmd: fix the output of the `Usage` section (#6138) * core: OnExit hooks (#6128) * core: OnExit callbacks * core: Process-global OnExit callbacks * ci: bump golangci/golangci-lint-action from 3 to 4 (#6141) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3 to 4. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3...v4) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Added more leaf certificate loaders tests and cleaned up code * Modified leaf cert loaders json field names and cleaned up storage loader comment * Update modules/caddytls/leaffileloader.go * Update LeafStorageLoader certificates field name * Upgraded protobuf version --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
e473ae6803
commit
3ae07a73dc
12 changed files with 649 additions and 4 deletions
67
caddytest/integration/leafcertloaders_test.go
Normal file
67
caddytest/integration/leafcertloaders_test.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2/caddytest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLeafCertLoaders(t *testing.T) {
|
||||||
|
tester := caddytest.NewTester(t)
|
||||||
|
tester.InitServer(`
|
||||||
|
{
|
||||||
|
"admin": {
|
||||||
|
"listen": "localhost:2999"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls_connection_policies": [
|
||||||
|
{
|
||||||
|
"client_authentication": {
|
||||||
|
"verifiers": [
|
||||||
|
{
|
||||||
|
"verifier": "leaf",
|
||||||
|
"leaf_certs_loaders": [
|
||||||
|
{
|
||||||
|
"loader": "file",
|
||||||
|
"files": ["../leafcert.pem"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"loader": "folder",
|
||||||
|
"folders": ["../"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"loader": "storage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"loader": "pem"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`, "json")
|
||||||
|
}
|
15
caddytest/leafcert.pem
Normal file
15
caddytest/leafcert.pem
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL
|
||||||
|
MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC
|
||||||
|
VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx
|
||||||
|
NDIxMTk0N1owVzELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlBOMQswCQYDVQQHEwJD
|
||||||
|
TjELMAkGA1UEChMCT04xCzAJBgNVBAsTAlVOMRQwEgYDVQQDEwtIZXJvbmcgWWFu
|
||||||
|
ZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCp5hnG7ogBhtlynpOS21cBewKE/B7j
|
||||||
|
V14qeyslnr26xZUsSVko36ZnhiaO/zbMOoRcKK9vEcgMtcLFuQTWDl3RAgMBAAGj
|
||||||
|
gbEwga4wHQYDVR0OBBYEFFXI70krXeQDxZgbaCQoR4jUDncEMH8GA1UdIwR4MHaA
|
||||||
|
FFXI70krXeQDxZgbaCQoR4jUDncEoVukWTBXMQswCQYDVQQGEwJDTjELMAkGA1UE
|
||||||
|
CBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMCVU4xFDAS
|
||||||
|
BgNVBAMTC0hlcm9uZyBZYW5nggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEE
|
||||||
|
BQADQQA/ugzBrjjK9jcWnDVfGHlk3icNRq0oV7Ri32z/+HQX67aRfgZu7KWdI+Ju
|
||||||
|
Wm7DCfrPNGVwFWUQOmsPue9rZBgO
|
||||||
|
-----END CERTIFICATE-----
|
2
go.mod
2
go.mod
|
@ -148,7 +148,7 @@ require (
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.16.1 // indirect
|
golang.org/x/tools v0.16.1 // indirect
|
||||||
google.golang.org/grpc v1.60.1 // indirect
|
google.golang.org/grpc v1.60.1 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
howett.net/plist v1.0.0 // indirect
|
howett.net/plist v1.0.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -855,6 +855,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
@ -651,7 +651,7 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
|
||||||
}
|
}
|
||||||
trustedLeafCerts = append(trustedLeafCerts, clientCert)
|
trustedLeafCerts = append(trustedLeafCerts, clientCert)
|
||||||
}
|
}
|
||||||
clientauth.verifiers = append(clientauth.verifiers, LeafCertClientAuth{TrustedLeafCerts: trustedLeafCerts})
|
clientauth.verifiers = append(clientauth.verifiers, LeafCertClientAuth{trustedLeafCerts: trustedLeafCerts})
|
||||||
}
|
}
|
||||||
|
|
||||||
// if a custom verification function already exists, wrap it
|
// if a custom verification function already exists, wrap it
|
||||||
|
@ -715,7 +715,8 @@ func setDefaultTLSParams(cfg *tls.Config) {
|
||||||
|
|
||||||
// LeafCertClientAuth verifies the client's leaf certificate.
|
// LeafCertClientAuth verifies the client's leaf certificate.
|
||||||
type LeafCertClientAuth struct {
|
type LeafCertClientAuth struct {
|
||||||
TrustedLeafCerts []*x509.Certificate
|
LeafCertificateLoadersRaw []json.RawMessage `json:"leaf_certs_loaders,omitempty" caddy:"namespace=tls.leaf_cert_loader inline_key=loader"`
|
||||||
|
trustedLeafCerts []*x509.Certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaddyModule returns the Caddy module information.
|
// CaddyModule returns the Caddy module information.
|
||||||
|
@ -726,6 +727,30 @@ func (LeafCertClientAuth) CaddyModule() caddy.ModuleInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LeafCertClientAuth) Provision(ctx caddy.Context) error {
|
||||||
|
if l.LeafCertificateLoadersRaw == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
val, err := ctx.LoadModule(l, "LeafCertificateLoadersRaw")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not parse leaf certificates loaders: %s", err.Error())
|
||||||
|
}
|
||||||
|
trustedLeafCertloaders := []LeafCertificateLoader{}
|
||||||
|
for _, loader := range val.([]any) {
|
||||||
|
trustedLeafCertloaders = append(trustedLeafCertloaders, loader.(LeafCertificateLoader))
|
||||||
|
}
|
||||||
|
trustedLeafCertificates := []*x509.Certificate{}
|
||||||
|
for _, loader := range trustedLeafCertloaders {
|
||||||
|
certs, err := loader.LoadLeafCertificates()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not load leaf certificates: %s", err.Error())
|
||||||
|
}
|
||||||
|
trustedLeafCertificates = append(trustedLeafCertificates, certs...)
|
||||||
|
}
|
||||||
|
l.trustedLeafCerts = trustedLeafCertificates
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||||
if len(rawCerts) == 0 {
|
if len(rawCerts) == 0 {
|
||||||
return fmt.Errorf("no client certificate provided")
|
return fmt.Errorf("no client certificate provided")
|
||||||
|
@ -736,7 +761,7 @@ func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x5
|
||||||
return fmt.Errorf("can't parse the given certificate: %s", err.Error())
|
return fmt.Errorf("can't parse the given certificate: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, trustedLeafCert := range l.TrustedLeafCerts {
|
for _, trustedLeafCert := range l.trustedLeafCerts {
|
||||||
if remoteLeafCert.Equal(trustedLeafCert) {
|
if remoteLeafCert.Equal(trustedLeafCert) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -765,6 +790,12 @@ type ConnectionMatcher interface {
|
||||||
Match(*tls.ClientHelloInfo) bool
|
Match(*tls.ClientHelloInfo) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LeafCertificateLoader is a type that loads the trusted leaf certificates
|
||||||
|
// for the tls.leaf_cert_loader modules
|
||||||
|
type LeafCertificateLoader interface {
|
||||||
|
LoadLeafCertificates() ([]*x509.Certificate, error)
|
||||||
|
}
|
||||||
|
|
||||||
// ClientCertificateVerifier is a type which verifies client certificates.
|
// ClientCertificateVerifier is a type which verifies client certificates.
|
||||||
// It is called during verifyPeerCertificate in the TLS handshake.
|
// It is called during verifyPeerCertificate in the TLS handshake.
|
||||||
type ClientCertificateVerifier interface {
|
type ClientCertificateVerifier interface {
|
||||||
|
|
99
modules/caddytls/leaffileloader.go
Normal file
99
modules/caddytls/leaffileloader.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
// 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 caddytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(LeafFileLoader{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeafFileLoader loads leaf certificates from disk.
|
||||||
|
type LeafFileLoader struct {
|
||||||
|
Files []string `json:"files,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision implements caddy.Provisioner.
|
||||||
|
func (fl *LeafFileLoader) Provision(ctx caddy.Context) error {
|
||||||
|
repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
if !ok {
|
||||||
|
repl = caddy.NewReplacer()
|
||||||
|
}
|
||||||
|
for k, path := range fl.Files {
|
||||||
|
fl.Files[k] = repl.ReplaceKnown(path, "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (LeafFileLoader) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "tls.leaf_cert_loader.file",
|
||||||
|
New: func() caddy.Module { return new(LeafFileLoader) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadLeafCertificates returns the certificates to be loaded by fl.
|
||||||
|
func (fl LeafFileLoader) LoadLeafCertificates() ([]*x509.Certificate, error) {
|
||||||
|
certificates := make([]*x509.Certificate, 0, len(fl.Files))
|
||||||
|
for _, path := range fl.Files {
|
||||||
|
ders, err := convertPEMFilesToDERBytes(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certs, err := x509.ParseCertificates(ders)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certificates = append(certificates, certs...)
|
||||||
|
}
|
||||||
|
return certificates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertPEMFilesToDERBytes(filename string) ([]byte, error) {
|
||||||
|
certDataPEM, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var ders []byte
|
||||||
|
// while block is not nil, we have more certificates in the file
|
||||||
|
for block, rest := pem.Decode(certDataPEM); block != nil; block, rest = pem.Decode(rest) {
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename)
|
||||||
|
}
|
||||||
|
ders = append(
|
||||||
|
ders,
|
||||||
|
block.Bytes...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// if we decoded nothing, return an error
|
||||||
|
if len(ders) == 0 {
|
||||||
|
return nil, fmt.Errorf("no CERTIFICATE pem block found in %s", filename)
|
||||||
|
}
|
||||||
|
return ders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface guard
|
||||||
|
var (
|
||||||
|
_ LeafCertificateLoader = (*LeafFileLoader)(nil)
|
||||||
|
_ caddy.Provisioner = (*LeafFileLoader)(nil)
|
||||||
|
)
|
38
modules/caddytls/leaffileloader_test.go
Normal file
38
modules/caddytls/leaffileloader_test.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package caddytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/pem"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLeafFileLoader(t *testing.T) {
|
||||||
|
fl := LeafFileLoader{Files: []string{"../../caddytest/leafcert.pem"}}
|
||||||
|
fl.Provision(caddy.Context{Context: context.Background()})
|
||||||
|
|
||||||
|
out, err := fl.LoadLeafCertificates()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Leaf certs file loading test failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(out) != 1 {
|
||||||
|
t.Errorf("Error loading leaf cert in memory struct")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: out[0].Raw})
|
||||||
|
|
||||||
|
pemFileBytes, err := os.ReadFile("../../caddytest/leafcert.pem")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to read the example certificate from the file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove /r because windows.
|
||||||
|
pemFileString := strings.ReplaceAll(string(pemFileBytes), "\r\n", "\n")
|
||||||
|
|
||||||
|
if string(pemBytes) != pemFileString {
|
||||||
|
t.Errorf("Leaf Certificate File Loader: Failed to load the correct certificate")
|
||||||
|
}
|
||||||
|
}
|
97
modules/caddytls/leaffolderloader.go
Normal file
97
modules/caddytls/leaffolderloader.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// 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 caddytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(LeafFolderLoader{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeafFolderLoader loads certificates and their associated keys from disk
|
||||||
|
// by recursively walking the specified directories, looking for PEM
|
||||||
|
// files which contain both a certificate and a key.
|
||||||
|
type LeafFolderLoader struct {
|
||||||
|
Folders []string `json:"folders,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (LeafFolderLoader) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "tls.leaf_cert_loader.folder",
|
||||||
|
New: func() caddy.Module { return new(LeafFolderLoader) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision implements caddy.Provisioner.
|
||||||
|
func (fl *LeafFolderLoader) Provision(ctx caddy.Context) error {
|
||||||
|
repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
if !ok {
|
||||||
|
repl = caddy.NewReplacer()
|
||||||
|
}
|
||||||
|
for k, path := range fl.Folders {
|
||||||
|
fl.Folders[k] = repl.ReplaceKnown(path, "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadLeafCertificates loads all the leaf certificates in the directories
|
||||||
|
// listed in fl from all files ending with .pem.
|
||||||
|
func (fl LeafFolderLoader) LoadLeafCertificates() ([]*x509.Certificate, error) {
|
||||||
|
var certs []*x509.Certificate
|
||||||
|
for _, dir := range fl.Folders {
|
||||||
|
err := filepath.Walk(dir, func(fpath string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to traverse into path: %s", fpath)
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(strings.ToLower(info.Name()), ".pem") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
certData, err := convertPEMFilesToDERBytes(fpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(certData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", fpath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certs = append(certs, cert)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return certs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ LeafCertificateLoader = (*LeafFolderLoader)(nil)
|
||||||
|
_ caddy.Provisioner = (*LeafFolderLoader)(nil)
|
||||||
|
)
|
37
modules/caddytls/leaffolderloader_test.go
Normal file
37
modules/caddytls/leaffolderloader_test.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package caddytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/pem"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLeafFolderLoader(t *testing.T) {
|
||||||
|
fl := LeafFolderLoader{Folders: []string{"../../caddytest"}}
|
||||||
|
fl.Provision(caddy.Context{Context: context.Background()})
|
||||||
|
|
||||||
|
out, err := fl.LoadLeafCertificates()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Leaf certs folder loading test failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(out) != 1 {
|
||||||
|
t.Errorf("Error loading leaf cert in memory struct")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: out[0].Raw})
|
||||||
|
pemFileBytes, err := os.ReadFile("../../caddytest/leafcert.pem")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to read the example certificate from the file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove /r because windows.
|
||||||
|
pemFileString := strings.ReplaceAll(string(pemFileBytes), "\r\n", "\n")
|
||||||
|
|
||||||
|
if string(pemBytes) != pemFileString {
|
||||||
|
t.Errorf("Leaf Certificate Folder Loader: Failed to load the correct certificate")
|
||||||
|
}
|
||||||
|
}
|
76
modules/caddytls/leafpemloader.go
Normal file
76
modules/caddytls/leafpemloader.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// 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 caddytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(LeafPEMLoader{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeafPEMLoader loads leaf certificates by
|
||||||
|
// decoding their PEM blocks directly. This has the advantage
|
||||||
|
// of not needing to store them on disk at all.
|
||||||
|
type LeafPEMLoader struct {
|
||||||
|
Certificates []string `json:"certificates,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision implements caddy.Provisioner.
|
||||||
|
func (pl *LeafPEMLoader) Provision(ctx caddy.Context) error {
|
||||||
|
repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
if !ok {
|
||||||
|
repl = caddy.NewReplacer()
|
||||||
|
}
|
||||||
|
for i, cert := range pl.Certificates {
|
||||||
|
pl.Certificates[i] = repl.ReplaceKnown(cert, "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (LeafPEMLoader) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "tls.leaf_cert_loader.pem",
|
||||||
|
New: func() caddy.Module { return new(LeafPEMLoader) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadLeafCertificates returns the certificates contained in pl.
|
||||||
|
func (pl LeafPEMLoader) LoadLeafCertificates() ([]*x509.Certificate, error) {
|
||||||
|
certs := make([]*x509.Certificate, 0, len(pl.Certificates))
|
||||||
|
for i, cert := range pl.Certificates {
|
||||||
|
derBytes, err := convertPEMToDER([]byte(cert))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("PEM leaf certificate loader, cert %d: %v", i, err)
|
||||||
|
}
|
||||||
|
cert, err := x509.ParseCertificate(derBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("PEM cert %d: %v", i, err)
|
||||||
|
}
|
||||||
|
certs = append(certs, cert)
|
||||||
|
}
|
||||||
|
return certs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface guard
|
||||||
|
var (
|
||||||
|
_ LeafCertificateLoader = (*LeafPEMLoader)(nil)
|
||||||
|
_ caddy.Provisioner = (*LeafPEMLoader)(nil)
|
||||||
|
)
|
54
modules/caddytls/leafpemloader_test.go
Normal file
54
modules/caddytls/leafpemloader_test.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package caddytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/pem"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLeafPEMLoader(t *testing.T) {
|
||||||
|
pl := LeafPEMLoader{Certificates: []string{`
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL
|
||||||
|
MAkGA1UECBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMC
|
||||||
|
VU4xFDASBgNVBAMTC0hlcm9uZyBZYW5nMB4XDTA1MDcxNTIxMTk0N1oXDTA1MDgx
|
||||||
|
NDIxMTk0N1owVzELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlBOMQswCQYDVQQHEwJD
|
||||||
|
TjELMAkGA1UEChMCT04xCzAJBgNVBAsTAlVOMRQwEgYDVQQDEwtIZXJvbmcgWWFu
|
||||||
|
ZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCp5hnG7ogBhtlynpOS21cBewKE/B7j
|
||||||
|
V14qeyslnr26xZUsSVko36ZnhiaO/zbMOoRcKK9vEcgMtcLFuQTWDl3RAgMBAAGj
|
||||||
|
gbEwga4wHQYDVR0OBBYEFFXI70krXeQDxZgbaCQoR4jUDncEMH8GA1UdIwR4MHaA
|
||||||
|
FFXI70krXeQDxZgbaCQoR4jUDncEoVukWTBXMQswCQYDVQQGEwJDTjELMAkGA1UE
|
||||||
|
CBMCUE4xCzAJBgNVBAcTAkNOMQswCQYDVQQKEwJPTjELMAkGA1UECxMCVU4xFDAS
|
||||||
|
BgNVBAMTC0hlcm9uZyBZYW5nggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEE
|
||||||
|
BQADQQA/ugzBrjjK9jcWnDVfGHlk3icNRq0oV7Ri32z/+HQX67aRfgZu7KWdI+Ju
|
||||||
|
Wm7DCfrPNGVwFWUQOmsPue9rZBgO
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
`}}
|
||||||
|
pl.Provision(caddy.Context{Context: context.Background()})
|
||||||
|
|
||||||
|
out, err := pl.LoadLeafCertificates()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Leaf certs pem loading test failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(out) != 1 {
|
||||||
|
t.Errorf("Error loading leaf cert in memory struct")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: out[0].Raw})
|
||||||
|
|
||||||
|
pemFileBytes, err := os.ReadFile("../../caddytest/leafcert.pem")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to read the example certificate from the file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove /r because windows.
|
||||||
|
pemFileString := strings.ReplaceAll(string(pemFileBytes), "\r\n", "\n")
|
||||||
|
|
||||||
|
if string(pemBytes) != pemFileString {
|
||||||
|
t.Errorf("Leaf Certificate Folder Loader: Failed to load the correct certificate")
|
||||||
|
}
|
||||||
|
}
|
129
modules/caddytls/leafstorageloader.go
Normal file
129
modules/caddytls/leafstorageloader.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
// 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 caddytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/caddyserver/certmagic"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(LeafStorageLoader{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeafStorageLoader loads leaf certificates from the
|
||||||
|
// globally configured storage module.
|
||||||
|
type LeafStorageLoader struct {
|
||||||
|
// A list of certificate file names to be loaded from storage.
|
||||||
|
Certificates []string `json:"certificates,omitempty"`
|
||||||
|
|
||||||
|
// The storage module where the trusted leaf certificates are stored. Absent
|
||||||
|
// explicit storage implies the use of Caddy default storage.
|
||||||
|
StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
|
||||||
|
|
||||||
|
// Reference to the globally configured storage module.
|
||||||
|
storage certmagic.Storage
|
||||||
|
|
||||||
|
ctx caddy.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (LeafStorageLoader) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "tls.leaf_cert_loader.storage",
|
||||||
|
New: func() caddy.Module { return new(LeafStorageLoader) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision loads the storage module for sl.
|
||||||
|
func (sl *LeafStorageLoader) Provision(ctx caddy.Context) error {
|
||||||
|
if sl.StorageRaw != nil {
|
||||||
|
val, err := ctx.LoadModule(sl, "StorageRaw")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading storage module: %v", err)
|
||||||
|
}
|
||||||
|
cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating storage configuration: %v", err)
|
||||||
|
}
|
||||||
|
sl.storage = cmStorage
|
||||||
|
}
|
||||||
|
if sl.storage == nil {
|
||||||
|
sl.storage = ctx.Storage()
|
||||||
|
}
|
||||||
|
sl.ctx = ctx
|
||||||
|
|
||||||
|
repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
if !ok {
|
||||||
|
repl = caddy.NewReplacer()
|
||||||
|
}
|
||||||
|
for k, path := range sl.Certificates {
|
||||||
|
sl.Certificates[k] = repl.ReplaceKnown(path, "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadLeafCertificates returns the certificates to be loaded by sl.
|
||||||
|
func (sl LeafStorageLoader) LoadLeafCertificates() ([]*x509.Certificate, error) {
|
||||||
|
certificates := make([]*x509.Certificate, 0, len(sl.Certificates))
|
||||||
|
for _, path := range sl.Certificates {
|
||||||
|
certData, err := sl.storage.Load(sl.ctx, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ders, err := convertPEMToDER(certData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certs, err := x509.ParseCertificates(ders)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certificates = append(certificates, certs...)
|
||||||
|
}
|
||||||
|
return certificates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertPEMToDER(pemData []byte) ([]byte, error) {
|
||||||
|
var ders []byte
|
||||||
|
// while block is not nil, we have more certificates in the file
|
||||||
|
for block, rest := pem.Decode(pemData); block != nil; block, rest = pem.Decode(rest) {
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
return nil, fmt.Errorf("no CERTIFICATE pem block found in the given pem data")
|
||||||
|
}
|
||||||
|
ders = append(
|
||||||
|
ders,
|
||||||
|
block.Bytes...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// if we decoded nothing, return an error
|
||||||
|
if len(ders) == 0 {
|
||||||
|
return nil, fmt.Errorf("no CERTIFICATE pem block found in the given pem data")
|
||||||
|
}
|
||||||
|
return ders, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface guard
|
||||||
|
var (
|
||||||
|
_ LeafCertificateLoader = (*LeafStorageLoader)(nil)
|
||||||
|
_ caddy.Provisioner = (*LeafStorageLoader)(nil)
|
||||||
|
)
|
Loading…
Reference in a new issue