Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding (m)TLS Support #615

Merged
merged 24 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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