Skip to content

Commit

Permalink
caddytls: clientauth: leaf verifier: make trusted leaf certs source p…
Browse files Browse the repository at this point in the history
…luggable (#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](golangci/golangci-lint-action@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] <[email protected]>
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] <[email protected]>
Co-authored-by: Mohammed Al Sahaf <[email protected]>
Co-authored-by: Matt Holt <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Mar 5, 2024
1 parent e473ae6 commit 3ae07a7
Show file tree
Hide file tree
Showing 12 changed files with 649 additions and 4 deletions.
67 changes: 67 additions & 0 deletions caddytest/integration/leafcertloaders_test.go
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions caddytest/leafcert.pem
Original file line number Diff line number Diff line change
@@ -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 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ require (
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.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
howett.net/plist v1.0.0 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
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/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=
Expand Down
37 changes: 34 additions & 3 deletions modules/caddytls/connpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
}
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
Expand Down Expand Up @@ -715,7 +715,8 @@ func setDefaultTLSParams(cfg *tls.Config) {

// LeafCertClientAuth verifies the client's leaf certificate.
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.
Expand All @@ -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 {
if len(rawCerts) == 0 {
return fmt.Errorf("no client certificate provided")
Expand All @@ -736,7 +761,7 @@ func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x5
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) {
return nil
}
Expand Down Expand Up @@ -765,6 +790,12 @@ type ConnectionMatcher interface {
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.
// It is called during verifyPeerCertificate in the TLS handshake.
type ClientCertificateVerifier interface {
Expand Down
99 changes: 99 additions & 0 deletions modules/caddytls/leaffileloader.go
Original file line number Diff line number Diff line change
@@ -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 changes: 38 additions & 0 deletions modules/caddytls/leaffileloader_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
Loading

0 comments on commit 3ae07a7

Please sign in to comment.