Skip to content

Commit

Permalink
Adding (m)TLS Support (#615)
Browse files Browse the repository at this point in the history
* rename dev proxy files

* Add {server,backend} tls configuration

* add server tls option and fixed h2 / proto logging

* prevent std log with unknown certificate client cancels

* use wd join for client certs

* add clientHello debug log

* fixup logging, snake case

* move self signed certificate method to own file

* move server related err wrapper to server file

* Added cert load tests and fixed bugs; switched to elliptic self signed

* Add server mtls support, backend tls client configuration; tests

small code fixups here and there

* Add merge option + test for tls blocks

* fixup merge

* Add server clientCert leaf test

* (ci) fixup github port permissions

* Add backend tls test

* add documentation

* Apply suggestions from code review

Co-authored-by: Johannes Koch <53434855+johakoch@users.noreply.github.com>

* Add client_certificate leaf or/and CA handling

* add internal counter for certificate creation to distinguish between them during testing

* Fix unstable test expectation; sort mergeDefinitions labels

* docs refinement

* Test Fixup; more sorting during config merge

* fixup test; happy race detector

Co-authored-by: Johannes Koch <53434855+johakoch@users.noreply.github.com>
  • Loading branch information
Marcel Ludwig and johakoch authored Nov 15, 2022
1 parent 0e0cab2 commit 9d0f8c7
Show file tree
Hide file tree
Showing 85 changed files with 2,545 additions and 483 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@
/docs/website/node_modules
/docs/website/.vscode/settings.json
docs/website/.vite

# tls
/*.crt
/*.key
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Unreleased changes are available as `avenga/couper:edge` container.
* **Added**
* [`trim()` function](https://docs.couper.io/configuration/functions) ([#605](https://github.com/avenga/couper/pull/605))
* OAuth2 client authentication methods (`token_endpoint_auth_method` values) `"client_secret_jwt"` and `"private_key_jwt"` including `jwt_signing_profile` block for [`oauth2`](https://docs.couper.io/configuration/block/oauth2_req_auth), [`beta_oauth2`](https://docs.couper.io/configuration/block/oauth2_ac) and [`oidc`](https://docs.couper.io/configuration/block/oidc) blocks ([#599](https://github.com/avenga/couper/pull/599))
* **mTLS** Support for [`server`](https://docs.couper.io/configuration/block/server_tls) and [`backend`](https://docs.couper.io/configuration/block/backend_tls) blocks ([#615](https://github.com/avenga/couper/pull/615))

* **Changed**
* Replaced the JWT library because the former library was no longer maintained ([#612](https://github.com/avenga/couper/pull/612))
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ test-coverage-show:
go tool cover -html=logging.coverage
go tool cover -html=server.coverage
go tool cover -html=main.coverage

.PHONY: mtls-certificates
mtls-certificates:
time go run internal/tls/cli/main.go
8 changes: 6 additions & 2 deletions command/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ func (r *Run) Execute(args Args, config *config.Couper, logEntry *logrus.Entry)
return err
}

// TODO: move to config validation
if config.Settings.SecureCookies != "" &&
config.Settings.SecureCookies != writer.SecureCookiesStrip {
return fmt.Errorf("invalid value for the -secure-cookies flag given: '%s' only 'strip' is supported", config.Settings.SecureCookies)
Expand Down Expand Up @@ -170,7 +171,10 @@ func (r *Run) Execute(args Args, config *config.Couper, logEntry *logrus.Entry)
}
}

serverList, listenCmdShutdown := server.NewServerList(r.context, config.Context, logEntry, config.Settings, &timings, srvConf)
servers, listenCmdShutdown, err := server.NewServers(r.context, config.Context, logEntry, config.Settings, &timings, srvConf)
if err != nil {
return err
}
var tlsServer []*http.Server

for mappedListenPort := range tlsDevPorts {
Expand All @@ -179,7 +183,7 @@ func (r *Run) Execute(args Args, config *config.Couper, logEntry *logrus.Entry)
}
}

for _, srv := range serverList {
for _, srv := range servers {
if listenErr := srv.Listen(); listenErr != nil {
return listenErr
}
Expand Down
6 changes: 2 additions & 4 deletions command/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package command
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"net/http/httptest"
"os"
Expand Down Expand Up @@ -259,7 +258,7 @@ func TestArgs_CAFile(t *testing.T) {

tmpFile, err := os.CreateTemp("", "ca.cert")
helper.Must(err)
_, err = tmpFile.Write(selfSigned.CA)
_, err = tmpFile.Write(selfSigned.CACertificate.Certificate)
helper.Must(err)
helper.Must(tmpFile.Close())
defer os.Remove(tmpFile.Name())
Expand Down Expand Up @@ -311,7 +310,6 @@ definitions {

port := couperFile.Settings.DefaultPort

fmt.Println(">>>>> START 2", time.Now())
// ensure the previous tests aren't listening
test.WaitForClosedPort(port)
go func() {
Expand Down Expand Up @@ -406,7 +404,7 @@ func TestReadCAFile(t *testing.T) {
ssc, err := server.NewCertificate(time.Minute, nil, nil)
helper.Must(err)

_, err = malformedFile.Write(ssc.CA[:100]) // incomplete
_, err = malformedFile.Write(ssc.CACertificate.Certificate[:100]) // incomplete
helper.Must(err)

_, err = readCertificateFile(malformedFile.Name())
Expand Down
21 changes: 10 additions & 11 deletions config/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,20 @@ var (
_ BackendReference = &Backend{}
_ Body = &Backend{}
_ Inline = &Backend{}

BackendInlineSchema = Backend{}.Schema(true)
)

// Backend represents the <Backend> object.
type Backend struct {
DisableCertValidation bool `hcl:"disable_certificate_validation,optional" docs:"Disables the peer certificate validation. Must not be used in backend refinement."`
DisableConnectionReuse bool `hcl:"disable_connection_reuse,optional" docs:"Disables reusage of connections to the origin. Must not be used in backend refinement."`
Health *Health `hcl:"beta_health,block"`
HTTP2 bool `hcl:"http2,optional" docs:"Enables the HTTP2 support. Must not be used in backend refinement."`
MaxConnections int `hcl:"max_connections,optional" docs:"The maximum number of concurrent connections in any state (_active_ or _idle_) to the origin. Must not be used in backend refinement." default:"0"`
Name string `hcl:"name,label,optional"`
OpenAPI *OpenAPI `hcl:"openapi,block"`
RateLimits RateLimits `hcl:"beta_rate_limit,block"`
Remain hcl.Body `hcl:",remain"`
DisableCertValidation bool `hcl:"disable_certificate_validation,optional" docs:"Disables the peer certificate validation. Must not be used in backend refinement."`
DisableConnectionReuse bool `hcl:"disable_connection_reuse,optional" docs:"Disables reusage of connections to the origin. Must not be used in backend refinement."`
Health *Health `hcl:"beta_health,block"`
HTTP2 bool `hcl:"http2,optional" docs:"Enables the HTTP2 support. Must not be used in backend refinement."`
MaxConnections int `hcl:"max_connections,optional" docs:"The maximum number of concurrent connections in any state (_active_ or _idle_) to the origin. Must not be used in backend refinement." default:"0"`
Name string `hcl:"name,label,optional"`
OpenAPI *OpenAPI `hcl:"openapi,block"`
RateLimits RateLimits `hcl:"beta_rate_limit,block"`
Remain hcl.Body `hcl:",remain"`
TLS *BackendTLS `hcl:"tls,block"`
}

// Reference implements the <BackendReference> interface.
Expand Down
9 changes: 9 additions & 0 deletions config/body/body.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ func BlocksOfType(body *hclsyntax.Body, blockType string) []*hclsyntax.Block {
return blocks
}

func AttributeByName(body *hclsyntax.Body, needle string) *hclsyntax.Attribute {
for _, attr := range body.Attributes {
if attr.Name == needle {
return attr
}
}
return nil
}

func RenameAttribute(body *hclsyntax.Body, old, new string) {
if attr, ok := body.Attributes[old]; ok {
attr.Name = new
Expand Down
17 changes: 17 additions & 0 deletions config/certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package config

type ClientCertificate struct {
Name string `hcl:",label,optional"`
CA string `hcl:"ca_certificate,optional" docs:"Public part of the certificate authority in DER or PEM format."`
CAFile string `hcl:"ca_certificate_file,optional" docs:"Public part of the certificate authority file in DER or PEM format."`
Leaf string `hcl:"leaf_certificate,optional" docs:"Public part of the client certificate in DER or PEM format."`
LeafFile string `hcl:"leaf_certificate_file,optional" docs:"Public part of the client certificate file in DER or PEM format."`
}

type ServerCertificate struct {
Name string `hcl:",label,optional"`
PublicKey string `hcl:"public_key,optional" docs:"Public part of the certificate in DER or PEM format."`
PublicKeyFile string `hcl:"public_key_file,optional" docs:"Public part of the certificate file in DER or PEM format."`
PrivateKey string `hcl:"private_key,optional" docs:"Private part of the certificate in DER or PEM format."`
PrivateKeyFile string `hcl:"private_key_file,optional" docs:"Private part of the certificate file in DER or PEM format."`
}
1 change: 1 addition & 0 deletions config/configload/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
server = "server"
settings = "settings"
spa = "spa"
tls = "tls"
tokenRequest = "beta_token_request"
// defaultNameLabel maps the hcl label attr 'name'.
defaultNameLabel = "default"
Expand Down
123 changes: 90 additions & 33 deletions config/configload/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ const (
errUniqueLabels = "All %s blocks must have unique labels."
)

type namedBlocks map[string]*hclsyntax.Block

func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block) (hclsyntax.Blocks, error) {
type (
namedBlocks map[string]*hclsyntax.Block
apiDefinition struct {
labels []string
typeRange hcl.Range
Expand Down Expand Up @@ -50,21 +51,29 @@ func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block)
attributes hclsyntax.Attributes
blocks namedBlocks
}
namedAPIs map[string]*apiDefinition
namedSPAs map[string]*spaDefinition
namedFiles map[string]*filesDefinition

tlsDefinition struct {
*hclsyntax.Block
blocks namedBlocks
}

namedAPIs map[string]*apiDefinition
namedSPAs map[string]*spaDefinition
namedFiles map[string]*filesDefinition

serverDefinition struct {
labels []string
typeRange hcl.Range
labelRanges []hcl.Range
openBraceRange hcl.Range
closeBraceRange hcl.Range
attributes hclsyntax.Attributes
apis namedAPIs
blocks namedBlocks
endpoints namedBlocks
apis namedAPIs
spas namedSPAs
files namedFiles
spas namedSPAs
tls *tlsDefinition
}

servers map[string]*serverDefinition
Expand All @@ -89,6 +98,9 @@ func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block)
attributes = hclsyntax.Attributes
blocks[<name>] = hclsyntax.Block (cors)
}
tls = {
blocks[<name>] = hclsyntax.Block (server_certificate|client_certificate)
}
}
*/

Expand Down Expand Up @@ -293,6 +305,25 @@ func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block)
for _, subBlock := range block.Body.Blocks {
results[serverKey].files[filesKey].blocks[subBlock.Type] = subBlock
}
} else if block.Type == tls {
if results[serverKey].tls == nil {
results[serverKey].tls = &tlsDefinition{
Block: block,
blocks: make(namedBlocks),
}
}

for name, attr := range block.Body.Attributes {
results[serverKey].tls.Body.Attributes[name] = attr
}

for _, subBlock := range block.Body.Blocks {
blockKey := ""
if len(subBlock.Labels) > 0 {
blockKey = subBlock.Labels[0]
}
results[serverKey].tls.blocks[blockKey] = subBlock
}
} else {
results[serverKey].blocks[block.Type] = block
}
Expand All @@ -302,30 +333,30 @@ func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block)

var mergedServers hclsyntax.Blocks

for _, serverBlock := range results {
for _, name := range getSortedMapKeys(results) {
var serverBlocks hclsyntax.Blocks

for _, b := range serverBlock.blocks {
serverBlocks = append(serverBlocks, b)
serverBlock := results[name]
for _, blockName := range getSortedMapKeys(serverBlock.blocks) {
serverBlocks = append(serverBlocks, serverBlock.blocks[blockName])
}

for _, b := range serverBlock.endpoints {
serverBlocks = append(serverBlocks, b)
for _, blockName := range getSortedMapKeys(serverBlock.endpoints) {
serverBlocks = append(serverBlocks, serverBlock.endpoints[blockName])
}

for _, apiBlock := range serverBlock.apis {
var apiBlocks hclsyntax.Blocks

for _, b := range apiBlock.blocks {
apiBlocks = append(apiBlocks, b)
for _, blockName := range getSortedMapKeys(apiBlock.blocks) {
apiBlocks = append(apiBlocks, apiBlock.blocks[blockName])
}

for _, b := range apiBlock.endpoints {
apiBlocks = append(apiBlocks, b)
for _, blockName := range getSortedMapKeys(apiBlock.endpoints) {
apiBlocks = append(apiBlocks, apiBlock.endpoints[blockName])
}

for _, b := range apiBlock.errorHandler {
apiBlocks = append(apiBlocks, b)
for _, blockName := range getSortedMapKeys(apiBlock.errorHandler) {
apiBlocks = append(apiBlocks, apiBlock.errorHandler[blockName])
}

mergedAPI := &hclsyntax.Block{
Expand All @@ -344,11 +375,12 @@ func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block)
serverBlocks = append(serverBlocks, mergedAPI)
}

for _, spaBlock := range serverBlock.spas {
for _, blockName := range getSortedMapKeys(serverBlock.spas) {
spaBlock := serverBlock.spas[blockName]
var spaBlocks hclsyntax.Blocks

for _, b := range spaBlock.blocks {
spaBlocks = append(spaBlocks, b)
for _, bn := range getSortedMapKeys(spaBlock.blocks) {
spaBlocks = append(spaBlocks, spaBlock.blocks[bn])
}

mergedSPA := &hclsyntax.Block{
Expand All @@ -367,11 +399,12 @@ func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block)
serverBlocks = append(serverBlocks, mergedSPA)
}

for _, filesBlock := range serverBlock.files {
for _, blockName := range getSortedMapKeys(serverBlock.files) {
filesBlock := serverBlock.files[blockName]
var filesBlocks hclsyntax.Blocks

for _, b := range filesBlock.blocks {
filesBlocks = append(filesBlocks, b)
for _, bn := range getSortedMapKeys(filesBlock.blocks) {
filesBlocks = append(filesBlocks, filesBlock.blocks[bn])
}

mergedFiles := &hclsyntax.Block{
Expand All @@ -390,6 +423,24 @@ func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block)
serverBlocks = append(serverBlocks, mergedFiles)
}

if serverBlock.tls != nil {
var tlsCertificateBlocks hclsyntax.Blocks
for _, blockName := range getSortedMapKeys(serverBlock.tls.blocks) {
tlsCertificateBlocks = append(tlsCertificateBlocks, serverBlock.tls.blocks[blockName])
}
serverBlocks = append(serverBlocks, &hclsyntax.Block{
Type: tls,
Body: &hclsyntax.Body{
Attributes: serverBlock.tls.Body.Attributes,
Blocks: tlsCertificateBlocks,
},
TypeRange: serverBlock.tls.TypeRange,
LabelRanges: serverBlock.tls.LabelRanges,
OpenBraceRange: serverBlock.tls.OpenBraceRange,
CloseBraceRange: serverBlock.tls.CloseBraceRange,
})
}

mergedServer := &hclsyntax.Block{
Type: server,
Labels: serverBlock.labels,
Expand All @@ -410,18 +461,15 @@ func mergeServers(bodies []*hclsyntax.Body, proxies map[string]*hclsyntax.Block)
}

func mergeDefinitions(bodies []*hclsyntax.Body) (*hclsyntax.Block, map[string]*hclsyntax.Block, error) {
type data map[string]*hclsyntax.Block
type list map[string]data

definitionsBlock := make(list)
proxiesList := make(data)
definitionsBlock := make(map[string]namedBlocks)
proxiesList := make(namedBlocks)

for _, body := range bodies {
for _, outerBlock := range body.Blocks {
if outerBlock.Type == definitions {
for _, innerBlock := range outerBlock.Body.Blocks {
if definitionsBlock[innerBlock.Type] == nil {
definitionsBlock[innerBlock.Type] = make(data)
definitionsBlock[innerBlock.Type] = make(namedBlocks)
}

if innerBlock.Type == backend {
Expand Down Expand Up @@ -483,9 +531,9 @@ func mergeDefinitions(bodies []*hclsyntax.Body) (*hclsyntax.Block, map[string]*h

var blocks []*hclsyntax.Block

for _, labels := range definitionsBlock {
for _, block := range labels {
blocks = append(blocks, block)
for _, name := range getSortedMapKeys(definitionsBlock) {
for _, label := range getSortedMapKeys(definitionsBlock[name]) {
blocks = append(blocks, definitionsBlock[name][label])
}
}

Expand Down Expand Up @@ -708,3 +756,12 @@ func newErrorHandlerKey(block *hclsyntax.Block) (key string) {

return strings.Join(sorted, errorHandlerLabelSep)
}

func getSortedMapKeys[K string, V any](m map[K]V) []string {
var result []string
for k := range m {
result = append(result, string(k))
}
sort.Strings(result)
return result
}
Loading

0 comments on commit 9d0f8c7

Please sign in to comment.