Skip to content

Commit

Permalink
simplified unified TLS config
Browse files Browse the repository at this point in the history
  • Loading branch information
FZambia committed Jan 6, 2025
1 parent 8cc73d9 commit bd248e2
Show file tree
Hide file tree
Showing 9 changed files with 524 additions and 100 deletions.
14 changes: 8 additions & 6 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,18 @@ func GetConfig(cmd *cobra.Command, configFile string) (Config, Meta, error) {
v := viper.NewWithOptions(viper.WithDecodeHook(mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
configtypes.StringToDurationHookFunc(),
configtypes.StringToPEMDataHookFunc(),
)))

if cmd != nil {
bindPFlags := []string{
"port", "address", "internal_port", "internal_address", "log_level", "log_file", "pid_file",
"engine.type", "broker.enabled", "broker.type", "presence_manager.enabled", "presence_manager.type",
"debug.enabled", "admin.enabled", "admin.external", "admin.insecure", "client.insecure", "http_api.insecure",
"http_api.external", "prometheus.enabled", "health.enabled", "grpc_api.enabled", "grpc_api.port",
"uni_grpc.enabled", "uni_grpc.port", "uni_websocket.enabled", "uni_sse.enabled", "uni_http_stream.enabled",
"sse.enabled", "http_stream.enabled", "swagger.enabled",
"pid_file", "http_server.port", "http_server.address", "http_server.internal_port",
"http_server.internal_address", "log.level", "log.file", "engine.type", "broker.enabled", "broker.type",
"presence_manager.enabled", "presence_manager.type", "debug.enabled", "admin.enabled", "admin.external",
"admin.insecure", "client.insecure", "http_api.insecure", "http_api.external", "prometheus.enabled",
"health.enabled", "grpc_api.enabled", "grpc_api.port", "uni_grpc.enabled", "uni_grpc.port",
"uni_websocket.enabled", "uni_sse.enabled", "uni_http_stream.enabled", "sse.enabled", "http_stream.enabled",
"swagger.enabled",
}
for _, flag := range bindPFlags {
_ = v.BindPFlag(flag, cmd.Flags().Lookup(flag))
Expand Down
4 changes: 4 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ func getConfig(t *testing.T, configFile string) (Config, Meta) {
func checkConfig(t *testing.T, conf Config) {
t.Helper()
require.NotNil(t, conf)
require.True(t, conf.HTTP.TLS.Enabled)
require.NotZero(t, conf.HTTP.TLS.ServerCAPem)
_, err := conf.HTTP.TLS.ToGoTLSConfig("test")
require.NoError(t, err)
require.Equal(t, "https://example.com/jwks", conf.Client.Token.JWKSPublicEndpoint)
require.Len(t, conf.Client.AllowedOrigins, 1)
require.Equal(t, "http://localhost:3000", conf.Client.AllowedOrigins[0])
Expand Down
3 changes: 2 additions & 1 deletion internal/config/testdata/config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"http_server": {
"tls": {
"enabled": true
"enabled": true,
"server_ca_pem": "-----BEGIN CERTIFICATE-----\nMIIEbjCCAtagAwIBAgIRAN1ZJEYl5ZNIOHsbJizQpucwDQYJKoZIhvcNAQELBQAw\ngZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnpA\nTWFjQm9vay1Qcm8tQWxleGFuZGVyLmxvY2FsIChBbGV4YW5kZXIgRW1lbGluKTFB\nMD8GA1UEAww4bWtjZXJ0IGZ6QE1hY0Jvb2stUHJvLUFsZXhhbmRlci5sb2NhbCAo\nQWxleGFuZGVyIEVtZWxpbikwHhcNMjIwNjE2MDYxOTM0WhcNMjQwOTE2MDYxOTM0\nWjBlMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxOjA4\nBgNVBAsMMWZ6QE1hY0Jvb2stUHJvLUFsZXhhbmRlci5sb2NhbCAoQWxleGFuZGVy\nIEVtZWxpbikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLCNVIle5k\nlfRtzjHe9sEo8zU9pqXfK9fxc2PZqfd6HVDVWyrOHNv9zWV8awEEgwX2kg+sY4ch\nuKmNdD19UWxLovCMkA92gKhzJoPPBMlVRtSA9QWNw4cXXB25KErPPyBXyyFA13X/\n6N408I26Aj6ewA0WLISkNgiCddUo31FygTNH4yWXF+F+lol0EJhG+K3E8diYub4P\n1Ul417sQ/1FxcoGo43fGl8j4y6wCnBQkSNaQCr1vvNEzdmiIYF02a51Efdb3PrSu\n90nJJBbFQxNhpcl98tLRF5t3wZJ+R2Xy4xPUZYwNNWTdICqW7a4bfD4foByp85kr\nu44kw7laXghhAgMBAAGjXjBcMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr\nBgEFBQcDATAfBgNVHSMEGDAWgBSMh55IrbevJTB4kiFUXsarEAIjXjAUBgNVHREE\nDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGBAG9yTMOybS6ike/UoIrj\nxLE3a9nPuFdt9anS0XgYicCYNFLc5H6MUXubsqBz30zigFbNP/FR2DKvIP+1cySP\nDKqnimTdxZWjzT9d0YHEYcD971yk/whXmKOcla2VmYMuPmUr6M3BmUmYcoWve/ML\nnc8qKJ+CsM80zxFSRbqCVqgPfNDzPHqGbJmOn0KbLPWzkUsIbii/O4IjqycJiDMS\nCyuat2Q8TYGiRhDJnouD/semDtqaIGGT77/5QLoEhFRwRKbOfgTT0hjLgTbeKPrx\nQKARxjVC/QF59nhdf+je/BgrF7jfR1UuCSxwl0xg2Ub2JB5A77efWEoQh2fuSgZk\nmVTZqDnfGvfYcGE9oiAMl21DimEAdYFSAUTtVI6T0S8BagN3jD+FLV7+TJgPiyIO\nLz9gcDP1Zn3jIp4Vy2HawWt+8rta351L70ie9Sk6Cx5fV0slvTFteWYdm26BuKbp\nNF7OqlGSRzM2iEVaMFLqnrRwDF4bR7qwGukppEXPrsAq2Q==\n-----END CERTIFICATE-----"
}
},
"engine": {
Expand Down
28 changes: 28 additions & 0 deletions internal/config/testdata/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

[http_server.tls]
enabled = true
server_ca_pem = """
-----BEGIN CERTIFICATE-----
MIIEbjCCAtagAwIBAgIRAN1ZJEYl5ZNIOHsbJizQpucwDQYJKoZIhvcNAQELBQAw
gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnpA
TWFjQm9vay1Qcm8tQWxleGFuZGVyLmxvY2FsIChBbGV4YW5kZXIgRW1lbGluKTFB
MD8GA1UEAww4bWtjZXJ0IGZ6QE1hY0Jvb2stUHJvLUFsZXhhbmRlci5sb2NhbCAo
QWxleGFuZGVyIEVtZWxpbikwHhcNMjIwNjE2MDYxOTM0WhcNMjQwOTE2MDYxOTM0
WjBlMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxOjA4
BgNVBAsMMWZ6QE1hY0Jvb2stUHJvLUFsZXhhbmRlci5sb2NhbCAoQWxleGFuZGVy
IEVtZWxpbikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLCNVIle5k
lfRtzjHe9sEo8zU9pqXfK9fxc2PZqfd6HVDVWyrOHNv9zWV8awEEgwX2kg+sY4ch
uKmNdD19UWxLovCMkA92gKhzJoPPBMlVRtSA9QWNw4cXXB25KErPPyBXyyFA13X/
6N408I26Aj6ewA0WLISkNgiCddUo31FygTNH4yWXF+F+lol0EJhG+K3E8diYub4P
1Ul417sQ/1FxcoGo43fGl8j4y6wCnBQkSNaQCr1vvNEzdmiIYF02a51Efdb3PrSu
90nJJBbFQxNhpcl98tLRF5t3wZJ+R2Xy4xPUZYwNNWTdICqW7a4bfD4foByp85kr
u44kw7laXghhAgMBAAGjXjBcMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
BgEFBQcDATAfBgNVHSMEGDAWgBSMh55IrbevJTB4kiFUXsarEAIjXjAUBgNVHREE
DTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGBAG9yTMOybS6ike/UoIrj
xLE3a9nPuFdt9anS0XgYicCYNFLc5H6MUXubsqBz30zigFbNP/FR2DKvIP+1cySP
DKqnimTdxZWjzT9d0YHEYcD971yk/whXmKOcla2VmYMuPmUr6M3BmUmYcoWve/ML
nc8qKJ+CsM80zxFSRbqCVqgPfNDzPHqGbJmOn0KbLPWzkUsIbii/O4IjqycJiDMS
Cyuat2Q8TYGiRhDJnouD/semDtqaIGGT77/5QLoEhFRwRKbOfgTT0hjLgTbeKPrx
QKARxjVC/QF59nhdf+je/BgrF7jfR1UuCSxwl0xg2Ub2JB5A77efWEoQh2fuSgZk
mVTZqDnfGvfYcGE9oiAMl21DimEAdYFSAUTtVI6T0S8BagN3jD+FLV7+TJgPiyIO
Lz9gcDP1Zn3jIp4Vy2HawWt+8rta351L70ie9Sk6Cx5fV0slvTFteWYdm26BuKbp
NF7OqlGSRzM2iEVaMFLqnrRwDF4bR7qwGukppEXPrsAq2Q==
-----END CERTIFICATE-----
"""

[engine]
type = "redis"
Expand Down
27 changes: 27 additions & 0 deletions internal/config/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@
http_server:
tls:
enabled: true
server_ca_pem: |
-----BEGIN CERTIFICATE-----
MIIEbjCCAtagAwIBAgIRAN1ZJEYl5ZNIOHsbJizQpucwDQYJKoZIhvcNAQELBQAw
gZ8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE6MDgGA1UECwwxZnpA
TWFjQm9vay1Qcm8tQWxleGFuZGVyLmxvY2FsIChBbGV4YW5kZXIgRW1lbGluKTFB
MD8GA1UEAww4bWtjZXJ0IGZ6QE1hY0Jvb2stUHJvLUFsZXhhbmRlci5sb2NhbCAo
QWxleGFuZGVyIEVtZWxpbikwHhcNMjIwNjE2MDYxOTM0WhcNMjQwOTE2MDYxOTM0
WjBlMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxOjA4
BgNVBAsMMWZ6QE1hY0Jvb2stUHJvLUFsZXhhbmRlci5sb2NhbCAoQWxleGFuZGVy
IEVtZWxpbikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLCNVIle5k
lfRtzjHe9sEo8zU9pqXfK9fxc2PZqfd6HVDVWyrOHNv9zWV8awEEgwX2kg+sY4ch
uKmNdD19UWxLovCMkA92gKhzJoPPBMlVRtSA9QWNw4cXXB25KErPPyBXyyFA13X/
6N408I26Aj6ewA0WLISkNgiCddUo31FygTNH4yWXF+F+lol0EJhG+K3E8diYub4P
1Ul417sQ/1FxcoGo43fGl8j4y6wCnBQkSNaQCr1vvNEzdmiIYF02a51Efdb3PrSu
90nJJBbFQxNhpcl98tLRF5t3wZJ+R2Xy4xPUZYwNNWTdICqW7a4bfD4foByp85kr
u44kw7laXghhAgMBAAGjXjBcMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
BgEFBQcDATAfBgNVHSMEGDAWgBSMh55IrbevJTB4kiFUXsarEAIjXjAUBgNVHREE
DTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGBAG9yTMOybS6ike/UoIrj
xLE3a9nPuFdt9anS0XgYicCYNFLc5H6MUXubsqBz30zigFbNP/FR2DKvIP+1cySP
DKqnimTdxZWjzT9d0YHEYcD971yk/whXmKOcla2VmYMuPmUr6M3BmUmYcoWve/ML
nc8qKJ+CsM80zxFSRbqCVqgPfNDzPHqGbJmOn0KbLPWzkUsIbii/O4IjqycJiDMS
Cyuat2Q8TYGiRhDJnouD/semDtqaIGGT77/5QLoEhFRwRKbOfgTT0hjLgTbeKPrx
QKARxjVC/QF59nhdf+je/BgrF7jfR1UuCSxwl0xg2Ub2JB5A77efWEoQh2fuSgZk
mVTZqDnfGvfYcGE9oiAMl21DimEAdYFSAUTtVI6T0S8BagN3jD+FLV7+TJgPiyIO
Lz9gcDP1Zn3jIp4Vy2HawWt+8rta351L70ie9Sk6Cx5fV0slvTFteWYdm26BuKbp
NF7OqlGSRzM2iEVaMFLqnrRwDF4bR7qwGukppEXPrsAq2Q==
-----END CERTIFICATE-----
engine:
type: redis
redis:
Expand Down
116 changes: 116 additions & 0 deletions internal/configtypes/pem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package configtypes

import (
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"reflect"

"github.com/go-viper/mapstructure/v2"
)

// PEMData represents a flexible PEM-encoded source.
// The order sources checked is the following:
// 1. Raw PEM content
// 2. Base64 encoded PEM content
// 3. Path to file with PEM content
type PEMData string

// String converts PEMData to a string.
func (p PEMData) String() string {
return string(p)
}

// MarshalJSON converts PEMData to JSON.
func (p PEMData) MarshalJSON() ([]byte, error) {
return json.Marshal(string(p))
}

// UnmarshalJSON parses PEMData from JSON.
func (p *PEMData) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
*p = PEMData(str)
return nil
}

// MarshalText converts PEMData to text for TOML.
func (p PEMData) MarshalText() ([]byte, error) {
return []byte(p.String()), nil
}

// UnmarshalText parses PEMData from text (used in TOML).
func (p *PEMData) UnmarshalText(text []byte) error {
*p = PEMData(text)
return nil
}

// MarshalYAML converts PEMData to a YAML-compatible format.
func (p PEMData) MarshalYAML() (interface{}, error) {
return p.String(), nil
}

// UnmarshalYAML parses PEMData from YAML.
func (p *PEMData) UnmarshalYAML(unmarshal func(interface{}) error) error {
var str string
if err := unmarshal(&str); err != nil {
return err
}
*p = PEMData(str)
return nil
}

// StringToPEMDataHookFunc for mapstructure to decode PEMData from strings.
func StringToPEMDataHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(PEMData("")) {
return data, nil
}

return PEMData(data.(string)), nil
}
}

// isValidPEM validates if the input string is a valid PEM block.
func isValidPEM(pemData string) bool {
// Decode the PEM data
block, _ := pem.Decode([]byte(pemData))
return block != nil
}

// Load detects if PEMData is a file path, base64 string, or raw PEM string and loads the content.
func (p PEMData) Load(statFile StatFileFunc, readFile ReadFileFunc) ([]byte, string, error) {
value := string(p)
if isValidPEM(value) {
return []byte(value), "raw pem", nil
}
// Check if it's base64 encoded.
if decodedValue, err := base64.StdEncoding.DecodeString(value); err == nil {
if isValidPEM(string(decodedValue)) {
return decodedValue, "base64 pem", nil
}
}
// Check if it's a file path by verifying if the file exists.
if _, err := statFile(value); err == nil {
content, err := readFile(value)
if err != nil {
return nil, "", fmt.Errorf("error reading file: %w", err)
}
if !isValidPEM(string(content)) {
return nil, "", fmt.Errorf("file \"%s\" contains invalid PEM data", value)
}
return content, "pem file path", nil
}
return nil, "", errors.New("invalid PEM data: not a valid file path, base64-encoded PEM content, or raw PEM content")
}
108 changes: 108 additions & 0 deletions internal/configtypes/pem_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package configtypes

import (
"encoding/base64"
"os"
"reflect"
"testing"

"github.com/stretchr/testify/assert"

"github.com/stretchr/testify/require"
)

func TestPEMData_Load(t *testing.T) {
mockStatFile := func(name string) (os.FileInfo, error) {
if name != "test-file" {
return nil, os.ErrNotExist
}
return nil, nil
}
mockReadFile := func(name string) ([]byte, error) {
if name != "test-file" {
return nil, os.ErrNotExist
}
return []byte(testPEMCert), nil
}

tests := []struct {
name string
data PEMData
expected string
}{
{"File Path", PEMData("test-file"), testPEMCert},
{"Base64", PEMData(base64.StdEncoding.EncodeToString([]byte(testPEMCert))), testPEMCert},
{"Raw PEM", PEMData(testPEMCert), testPEMCert},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, _, err := tt.data.Load(mockStatFile, mockReadFile)
require.NoError(t, err)
require.Equal(t, tt.expected, string(data))
})
}
}

func TestStringToPEMDataHookFunc(t *testing.T) {
hook := StringToPEMDataHookFunc().(func(
f reflect.Type,
t reflect.Type,
data interface{},
) (interface{}, error))
result, err := hook(reflect.TypeOf(""), reflect.TypeOf(PEMData("")), "test-data")
require.NoError(t, err)
require.Equal(t, PEMData("test-data"), result)
}

const testPEMCert = `
-- GlobalSign Root R2, valid until Dec 15, 2021
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----`

func Test_isValidPEM(t *testing.T) {
tests := []struct {
name string
data string
expected bool
}{
{"Valid PEM", testPEMCert, true},
{"Invalid PEM", "/etc/passwd", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isValidPEM(tt.data)
require.Equal(t, tt.expected, result)
})
}
}

const invalidPEM = "invalid PEM content"

func TestPEMData_Load_Invalid(t *testing.T) {
pem := PEMData(invalidPEM)

_, _, err := pem.Load(mockStatFileSuccess, mockReadFileInvalid)
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid PEM data")
}
Loading

0 comments on commit bd248e2

Please sign in to comment.