1
Fork 0
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:
Aziz Rmadi 2024-03-05 15:55:37 -06:00 committed by GitHub
parent e473ae6803
commit 3ae07a73dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 649 additions and 4 deletions

View 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
View 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
View file

@ -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
View file

@ -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=

View file

@ -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 {

View 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)
)

View 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")
}
}

View 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)
)

View 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")
}
}

View 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)
)

View 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")
}
}

View 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)
)