package configuration_test import ( "bytes" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/pem" "fmt" "math" "net" "net/mail" "net/url" "os" "path" "reflect" "regexp" "strings" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/authelia/authelia/v4/internal/configuration" "github.com/authelia/authelia/v4/internal/configuration/schema" ) func TestStringToMailAddressHookFunc(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeMailAddress", have: "james@example.com", want: mail.Address{Name: "", Address: "james@example.com"}, decode: true, }, { desc: "ShouldDecodeMailAddressWithName", have: "James ", want: mail.Address{Name: "James", Address: "james@example.com"}, decode: true, }, { desc: "ShouldDecodeMailAddressWithEmptyString", have: "", want: mail.Address{}, decode: true, }, { desc: "ShouldNotDecodeInvalidMailAddress", have: "fred", want: mail.Address{}, err: "could not decode 'fred' to a mail.Address (RFC5322): mail: missing '@' or angle-addr", decode: true, }, } hook := configuration.StringToMailAddressHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestStringToMailAddressHookFuncPointer(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeMailAddress", have: "james@example.com", want: &mail.Address{Name: "", Address: "james@example.com"}, decode: true, }, { desc: "ShouldDecodeMailAddressWithName", have: "James ", want: &mail.Address{Name: "James", Address: "james@example.com"}, decode: true, }, { desc: "ShouldDecodeMailAddressWithEmptyString", have: "", want: (*mail.Address)(nil), decode: true, }, { desc: "ShouldNotDecodeInvalidMailAddress", have: "fred", want: &mail.Address{}, err: "could not decode 'fred' to a *mail.Address (RFC5322): mail: missing '@' or angle-addr", decode: true, }, { desc: "ShouldNotDecodeToInt", have: "fred", want: testInt32Ptr(4), decode: false, }, } hook := configuration.StringToMailAddressHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestStringToURLHookFunc(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeURL", have: "https://www.example.com:9090/abc?test=true", want: url.URL{Scheme: "https", Host: "www.example.com:9090", Path: "/abc", RawQuery: "test=true"}, decode: true, }, { desc: "ShouldDecodeURLEmptyString", have: "", want: url.URL{}, decode: true, }, { desc: "ShouldNotDecodeToString", have: "abc", want: "", decode: false, }, { desc: "ShouldDecodeURLWithUserAndPassword", have: "https://john:abc123@www.example.com:9090/abc?test=true", want: url.URL{Scheme: "https", Host: "www.example.com:9090", Path: "/abc", RawQuery: "test=true", User: url.UserPassword("john", "abc123")}, decode: true, }, { desc: "ShouldNotDecodeInt", have: 5, want: url.URL{}, decode: false, }, { desc: "ShouldNotDecodeBool", have: true, want: url.URL{}, decode: false, }, { desc: "ShouldNotDecodeBadURL", have: "*(!&@#(!*^$%", want: url.URL{}, err: "could not decode '*(!&@#(!*^$%' to a url.URL: parse \"*(!&@#(!*^$%\": invalid URL escape \"%\"", decode: true, }, } hook := configuration.StringToURLHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestStringToURLHookFuncPointer(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeURL", have: "https://www.example.com:9090/abc?test=true", want: &url.URL{Scheme: "https", Host: "www.example.com:9090", Path: "/abc", RawQuery: "test=true"}, decode: true, }, { desc: "ShouldDecodeURLEmptyString", have: "", want: (*url.URL)(nil), decode: true, }, { desc: "ShouldDecodeURLWithUserAndPassword", have: "https://john:abc123@www.example.com:9090/abc?test=true", want: &url.URL{Scheme: "https", Host: "www.example.com:9090", Path: "/abc", RawQuery: "test=true", User: url.UserPassword("john", "abc123")}, decode: true, }, { desc: "ShouldNotDecodeInt", have: 5, want: &url.URL{}, decode: false, }, { desc: "ShouldNotDecodeBool", have: true, want: &url.URL{}, decode: false, }, { desc: "ShouldNotDecodeBadURL", have: "*(!&@#(!*^$%", want: &url.URL{}, err: "could not decode '*(!&@#(!*^$%' to a *url.URL: parse \"*(!&@#(!*^$%\": invalid URL escape \"%\"", decode: true, }, { desc: "ShouldNotDecodeToInt", have: "fred", want: testInt32Ptr(4), decode: false, }, } hook := configuration.StringToURLHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestToTimeDurationHookFunc(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeFourtyFiveSeconds", have: "45s", want: time.Second * 45, decode: true, }, { desc: "ShouldDecodeOneMinute", have: "1m", want: time.Minute, decode: true, }, { desc: "ShouldDecodeTwoHours", have: "2h", want: time.Hour * 2, decode: true, }, { desc: "ShouldDecodeThreeDays", have: "3d", want: time.Hour * 24 * 3, decode: true, }, { desc: "ShouldDecodeFourWeeks", have: "4w", want: time.Hour * 24 * 7 * 4, decode: true, }, { desc: "ShouldDecodeFiveMonths", have: "5M", want: time.Hour * 24 * 30 * 5, decode: true, }, { desc: "ShouldDecodeSixYears", have: "6y", want: time.Hour * 24 * 365 * 6, decode: true, }, { desc: "ShouldNotDecodeInvalidString", have: "abc", want: time.Duration(0), err: "could not decode 'abc' to a time.Duration: could not parse 'abc' as a duration", decode: true, }, { desc: "ShouldDecodeIntToSeconds", have: 60, want: time.Second * 60, decode: true, }, { desc: "ShouldDecodeInt8ToSeconds", have: int8(90), want: time.Second * 90, decode: true, }, { desc: "ShouldDecodeInt16ToSeconds", have: int16(90), want: time.Second * 90, decode: true, }, { desc: "ShouldDecodeInt32ToSeconds", have: int32(90), want: time.Second * 90, decode: true, }, { desc: "ShouldDecodeFloat64ToSeconds", have: float64(90), want: time.Second * 90, decode: true, }, { desc: "ShouldDecodeFloat64ToSeconds", have: math.MaxFloat64, want: time.Duration(math.MaxInt64), decode: true, }, { desc: "ShouldDecodeInt64ToSeconds", have: int64(120), want: time.Second * 120, decode: true, }, { desc: "ShouldDecodeTimeDuration", have: time.Second * 30, want: time.Second * 30, decode: true, }, { desc: "ShouldNotDecodeToString", have: int64(30), want: "", decode: false, }, { desc: "ShouldDecodeFromIntZero", have: 0, want: time.Duration(0), decode: true, }, { desc: "ShouldSkipParsingBoolean", have: true, want: time.Duration(0), decode: false, }, { desc: "ShouldNotDecodeFromBool", have: true, want: true, }, } hook := configuration.ToTimeDurationHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestToTimeDurationHookFuncPointer(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeFourtyFiveSeconds", have: "45s", want: testTimeDurationPtr(time.Second * 45), decode: true, }, { desc: "ShouldDecodeOneMinute", have: "1m", want: testTimeDurationPtr(time.Minute), decode: true, }, { desc: "ShouldDecodeTwoHours", have: "2h", want: testTimeDurationPtr(time.Hour * 2), decode: true, }, { desc: "ShouldDecodeThreeDays", have: "3d", want: testTimeDurationPtr(time.Hour * 24 * 3), decode: true, }, { desc: "ShouldDecodeFourWeeks", have: "4w", want: testTimeDurationPtr(time.Hour * 24 * 7 * 4), decode: true, }, { desc: "ShouldDecodeFiveMonths", have: "5M", want: testTimeDurationPtr(time.Hour * 24 * 30 * 5), decode: true, }, { desc: "ShouldDecodeSixYears", have: "6y", want: testTimeDurationPtr(time.Hour * 24 * 365 * 6), decode: true, }, { desc: "ShouldNotDecodeInvalidString", have: "abc", want: testTimeDurationPtr(time.Duration(0)), err: "could not decode 'abc' to a *time.Duration: could not parse 'abc' as a duration", decode: true, }, { desc: "ShouldDecodeIntToSeconds", have: 60, want: testTimeDurationPtr(time.Second * 60), decode: true, }, { desc: "ShouldDecodeInt32ToSeconds", have: int32(90), want: testTimeDurationPtr(time.Second * 90), decode: true, }, { desc: "ShouldDecodeInt64ToSeconds", have: int64(120), want: testTimeDurationPtr(time.Second * 120), decode: true, }, { desc: "ShouldDecodeTimeDuration", have: time.Second * 30, want: testTimeDurationPtr(time.Second * 30), decode: true, }, { desc: "ShouldNotDecodeToString", have: int64(30), want: &testString, decode: false, }, { desc: "ShouldDecodeFromIntZero", have: 0, want: testTimeDurationPtr(time.Duration(0)), decode: true, }, { desc: "ShouldNotDecodeFromBool", have: true, want: &testTrue, decode: false, }, } hook := configuration.ToTimeDurationHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestToRefreshIntervalDurationHookFunc(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeFourtyFiveSeconds", have: "45s", want: schema.NewRefreshIntervalDuration(time.Second * 45), decode: true, }, { desc: "ShouldDecodeOneMinute", have: "1m", want: schema.NewRefreshIntervalDuration(time.Minute), decode: true, }, { desc: "ShouldDecodeTwoHours", have: "2h", want: schema.NewRefreshIntervalDuration(time.Hour * 2), decode: true, }, { desc: "ShouldDecodeThreeDays", have: "3d", want: schema.NewRefreshIntervalDuration(time.Hour * 24 * 3), decode: true, }, { desc: "ShouldDecodeFourWeeks", have: "4w", want: schema.NewRefreshIntervalDuration(time.Hour * 24 * 7 * 4), decode: true, }, { desc: "ShouldDecodeFiveMonths", have: "5M", want: schema.NewRefreshIntervalDuration(time.Hour * 24 * 30 * 5), decode: true, }, { desc: "ShouldDecodeSixYears", have: "6y", want: schema.NewRefreshIntervalDuration(time.Hour * 24 * 365 * 6), decode: true, }, { desc: "ShouldNotDecodeInvalidString", have: "abc", want: schema.RefreshIntervalDuration{}, err: "could not decode 'abc' to a schema.RefreshIntervalDuration: could not parse 'abc' as a duration", decode: true, }, { desc: "ShouldDecodeIntToSeconds", have: 60, want: schema.NewRefreshIntervalDuration(time.Second * 60), decode: true, }, { desc: "ShouldDecodeInt8ToSeconds", have: int8(90), want: schema.NewRefreshIntervalDuration(time.Second * 90), decode: true, }, { desc: "ShouldDecodeInt16ToSeconds", have: int16(90), want: schema.NewRefreshIntervalDuration(time.Second * 90), decode: true, }, { desc: "ShouldDecodeInt32ToSeconds", have: int32(90), want: schema.NewRefreshIntervalDuration(time.Second * 90), decode: true, }, { desc: "ShouldDecodeFloat64ToSeconds", have: float64(90), want: schema.NewRefreshIntervalDuration(time.Second * 90), decode: true, }, { desc: "ShouldDecodeFloat64ToSeconds", have: math.MaxFloat64, want: schema.NewRefreshIntervalDuration(time.Duration(math.MaxInt64)), decode: true, }, { desc: "ShouldDecodeInt64ToSeconds", have: int64(120), want: schema.NewRefreshIntervalDuration(time.Second * 120), decode: true, }, { desc: "ShouldDecodeTimeDuration", have: time.Second * 30, want: schema.NewRefreshIntervalDuration(time.Second * 30), decode: true, }, { desc: "ShouldNotDecodeToString", have: int64(30), want: "", decode: false, }, { desc: "ShouldDecodeFromIntZero", have: 0, want: schema.NewRefreshIntervalDuration(time.Duration(0)), decode: true, }, { desc: "ShouldSkipParsingBoolean", have: true, want: schema.RefreshIntervalDuration{}, decode: false, }, { desc: "ShouldNotDecodeFromBool", have: true, want: true, }, } hook := configuration.ToRefreshIntervalDurationHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestTestToRefreshIntervalDurationHookFuncPointer(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeFourtyFiveSeconds", have: "45s", want: testRefreshIntervalDurationPtr(time.Second * 45), decode: true, }, { desc: "ShouldDecodeOneMinute", have: "1m", want: testRefreshIntervalDurationPtr(time.Minute), decode: true, }, { desc: "ShouldDecodeTwoHours", have: "2h", want: testRefreshIntervalDurationPtr(time.Hour * 2), decode: true, }, { desc: "ShouldDecodeThreeDays", have: "3d", want: testRefreshIntervalDurationPtr(time.Hour * 24 * 3), decode: true, }, { desc: "ShouldDecodeFourWeeks", have: "4w", want: testRefreshIntervalDurationPtr(time.Hour * 24 * 7 * 4), decode: true, }, { desc: "ShouldDecodeFiveMonths", have: "5M", want: testRefreshIntervalDurationPtr(time.Hour * 24 * 30 * 5), decode: true, }, { desc: "ShouldDecodeSixYears", have: "6y", want: testRefreshIntervalDurationPtr(time.Hour * 24 * 365 * 6), decode: true, }, { desc: "ShouldNotDecodeInvalidString", have: "abc", want: testRefreshIntervalDurationPtr(time.Duration(0)), err: "could not decode 'abc' to a *schema.RefreshIntervalDuration: could not parse 'abc' as a duration", decode: true, }, { desc: "ShouldDecodeIntToSeconds", have: 60, want: testRefreshIntervalDurationPtr(time.Second * 60), decode: true, }, { desc: "ShouldDecodeInt32ToSeconds", have: int32(90), want: testRefreshIntervalDurationPtr(time.Second * 90), decode: true, }, { desc: "ShouldDecodeInt64ToSeconds", have: int64(120), want: testRefreshIntervalDurationPtr(time.Second * 120), decode: true, }, { desc: "ShouldDecodeTimeDuration", have: time.Second * 30, want: testRefreshIntervalDurationPtr(time.Second * 30), decode: true, }, { desc: "ShouldNotDecodeToString", have: int64(30), want: &testString, decode: false, }, { desc: "ShouldDecodeFromIntZero", have: 0, want: testRefreshIntervalDurationPtr(time.Duration(0)), decode: true, }, { desc: "ShouldNotDecodeFromBool", have: true, want: &testTrue, decode: false, }, } hook := configuration.ToRefreshIntervalDurationHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestStringToRegexpFunc(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool wantGrps []string }{ { desc: "ShouldNotDecodeRegexpWithOpenParenthesis", have: "hello(test one two", want: regexp.Regexp{}, err: "could not decode 'hello(test one two' to a regexp.Regexp: error parsing regexp: missing closing ): `hello(test one two`", decode: true, }, { desc: "ShouldDecodeValidRegex", have: "^(api|admin)$", want: *regexp.MustCompile(`^(api|admin)$`), decode: true, }, { desc: "ShouldDecodeValidRegexWithGroupNames", have: "^(?Papi|admin)(one|two)$", want: *regexp.MustCompile(`^(?Papi|admin)(one|two)$`), decode: true, wantGrps: []string{"area"}, }, { desc: "ShouldNotDecodeFromInt32", have: int32(20), want: regexp.Regexp{}, decode: false, }, { desc: "ShouldNotDecodeFromBool", have: false, want: regexp.Regexp{}, decode: false, }, { desc: "ShouldNotDecodeToBool", have: "^(?Papi|admin)(one|two)$", want: testTrue, decode: false, }, { desc: "ShouldNotDecodeToInt32", have: "^(?Papi|admin)(one|two)$", want: testInt32Ptr(0), decode: false, }, { desc: "ShouldNotDecodeToMailAddress", have: "^(?Papi|admin)(one|two)$", want: mail.Address{}, decode: false, }, { desc: "ShouldErrOnDecodeEmptyString", have: "", want: regexp.Regexp{}, err: "could not decode an empty value to a regexp.Regexp: must have a non-empty value", decode: true, }, } hook := configuration.StringToRegexpHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) var names []string pattern := result.(regexp.Regexp) for _, name := range pattern.SubexpNames() { if name != "" { names = append(names, name) } } if len(tc.wantGrps) != 0 { t.Run("MustHaveAllExpectedSubexpGroupNames", func(t *testing.T) { for _, name := range tc.wantGrps { assert.Contains(t, names, name) } }) t.Run("MustNotHaveUnexpectedSubexpGroupNames", func(t *testing.T) { for _, name := range names { assert.Contains(t, tc.wantGrps, name) } }) } else { t.Run("MustHaveNoSubexpGroupNames", func(t *testing.T) { assert.Len(t, names, 0) }) } default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestStringToRegexpFuncPointers(t *testing.T) { testCases := []struct { desc string have any want any err string decode bool wantGrps []string }{ { desc: "ShouldNotDecodeRegexpWithOpenParenthesis", have: "hello(test one two", want: ®exp.Regexp{}, err: "could not decode 'hello(test one two' to a *regexp.Regexp: error parsing regexp: missing closing ): `hello(test one two`", decode: true, }, { desc: "ShouldDecodeValidRegex", have: "^(api|admin)$", want: regexp.MustCompile(`^(api|admin)$`), decode: true, }, { desc: "ShouldDecodeValidRegexWithGroupNames", have: "^(?Papi|admin)(one|two)$", want: regexp.MustCompile(`^(?Papi|admin)(one|two)$`), decode: true, wantGrps: []string{"area"}, }, { desc: "ShouldNotDecodeFromInt32", have: int32(20), want: ®exp.Regexp{}, decode: false, }, { desc: "ShouldNotDecodeFromBool", have: false, want: ®exp.Regexp{}, decode: false, }, { desc: "ShouldNotDecodeToBool", have: "^(?Papi|admin)(one|two)$", want: &testTrue, decode: false, }, { desc: "ShouldNotDecodeToInt32", have: "^(?Papi|admin)(one|two)$", want: &testZero, decode: false, }, { desc: "ShouldNotDecodeToMailAddress", have: "^(?Papi|admin)(one|two)$", want: &mail.Address{}, decode: false, }, { desc: "ShouldDecodeEmptyStringToNil", have: "", want: (*regexp.Regexp)(nil), decode: true, }, } hook := configuration.StringToRegexpHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) pattern := result.(*regexp.Regexp) if tc.want == (*regexp.Regexp)(nil) { assert.Nil(t, pattern) } else { var names []string for _, name := range pattern.SubexpNames() { if name != "" { names = append(names, name) } } if len(tc.wantGrps) != 0 { t.Run("MustHaveAllExpectedSubexpGroupNames", func(t *testing.T) { for _, name := range tc.wantGrps { assert.Contains(t, names, name) } }) t.Run("MustNotHaveUnexpectedSubexpGroupNames", func(t *testing.T) { for _, name := range names { assert.Contains(t, tc.wantGrps, name) } }) } else { t.Run("MustHaveNoSubexpGroupNames", func(t *testing.T) { assert.Len(t, names, 0) }) } } default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestStringToAddressHookFunc(t *testing.T) { testCases := []struct { name string have any expected any err string decode bool }{ { name: "ShouldDecodeNonPtr", have: "tcp://0.0.0.0:2020", expected: MustParseAddress("tcp://0.0.0.0:2020"), decode: true, }, { name: "ShouldDecodePtr", have: "tcp://0.0.0.0:2020", expected: MustParseAddressPtr("tcp://0.0.0.0:2020"), decode: true, }, { name: "ShouldNotDecodeIntegerToCorrectType", have: 1, expected: schema.Address{}, decode: false, }, { name: "ShouldNotDecodeIntegerToCorrectTypePtr", have: 1, expected: &schema.Address{}, decode: false, }, { name: "ShouldNotDecodeIntegerPtrToCorrectType", have: testInt32Ptr(1), expected: schema.Address{}, decode: false, }, { name: "ShouldNotDecodeIntegerPtrToCorrectTypePtr", have: testInt32Ptr(1), expected: &schema.Address{}, decode: false, }, { name: "ShouldNotDecodeToString", have: "tcp://0.0.0.0:2020", expected: "", decode: false, }, { name: "ShouldNotDecodeToIntPtr", have: "tcp://0.0.0.0:2020", expected: testInt32Ptr(1), decode: false, }, { name: "ShouldNotDecodeToIntPtr", have: "tcp://0.0.0.0:2020", expected: testInt32Ptr(1), decode: false, }, { name: "ShouldFailDecode", have: "tcp://&!@^#*&!@#&*@!:2020", expected: schema.Address{}, err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a schema.Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [://][:]: parse \"tcp://&!@^\": invalid character \"^\" in host name", decode: false, }, { name: "ShouldDecodeTCP", have: "tcp://127.0.0.1", expected: schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")}, err: "", decode: true, }, { name: "ShouldDecodeTCPPtr", have: "tcp://127.0.0.1", expected: &schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")}, err: "", decode: true, }, { name: "ShouldDecodeUDP", have: "udp://127.0.0.1", expected: schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")}, err: "", decode: true, }, { name: "ShouldDecodeUDPPtr", have: "udp://127.0.0.1", expected: &schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")}, err: "", decode: true, }, { name: "ShouldDecodeLDAP", have: "ldap://127.0.0.1", expected: schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")}, err: "", decode: true, }, { name: "ShouldDecodeLDAPPtr", have: "ldap://127.0.0.1", expected: &schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")}, err: "", decode: true, }, { name: "ShouldDecodeSMTP", have: "smtp://127.0.0.1", expected: schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")}, err: "", decode: true, }, { name: "ShouldDecodeSMTPPtr", have: "smtp://127.0.0.1", expected: &schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")}, err: "", decode: true, }, { name: "ShouldFailDecodeTCP", have: "@@@@@@@", expected: schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")}, err: "could not decode '@@@@@@@' to a schema.AddressTCP: error validating the address: the url 'tcp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses", decode: false, }, { name: "ShouldFailDecodeUDP", have: "@@@@@@@", expected: schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")}, err: "could not decode '@@@@@@@' to a schema.AddressUDP: error validating the address: the url 'udp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses", decode: false, }, { name: "ShouldFailDecodeLDAP", have: "@@@@@@@", expected: schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")}, err: "could not decode '@@@@@@@' to a schema.AddressLDAP: error validating the address: the url 'ldaps://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses", decode: false, }, { name: "ShouldFailDecodeSMTP", have: "@@@@@@@", expected: schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")}, err: "could not decode '@@@@@@@' to a schema.AddressSMTP: error validating the address: the url 'smtp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses", decode: false, }, } hook := configuration.StringToAddressHookFunc() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have) if tc.err != "" { assert.EqualError(t, err, tc.err) if !tc.decode { assert.Nil(t, actual) } } else { assert.NoError(t, err) if tc.decode { assert.Equal(t, tc.expected, actual) } else { assert.Equal(t, tc.have, actual) } } }) } } func TestStringToPrivateKeyHookFunc(t *testing.T) { var ( nilRSA *rsa.PrivateKey nilECDSA *ecdsa.PrivateKey nilCert *x509.Certificate ) testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeRSA1024PrivateKey", have: x509PrivateKeyRSA1024, want: MustParsePKCS8RSAPrivateKey(x509PrivateKeyRSA1024), decode: true, }, { desc: "ShouldDecodeRSA2048PrivateKey", have: x509PrivateKeyRSA2048, want: MustParsePKCS8RSAPrivateKey(x509PrivateKeyRSA2048), decode: true, }, { desc: "ShouldDecodeRSA4096PrivateKey", have: x509PrivateKeyRSA4096, want: MustParsePKCS8RSAPrivateKey(x509PrivateKeyRSA4096), decode: true, }, { desc: "ShouldDecodeECDSAP224PrivateKey", have: x509PrivateKeyECDSAP224, want: MustParsePKCS8ECDSAPrivateKey(x509PrivateKeyECDSAP224), decode: true, }, { desc: "ShouldDecodeECDSAP256PrivateKey", have: x509PrivateKeyECDSAP256, want: MustParsePKCS8ECDSAPrivateKey(x509PrivateKeyECDSAP256), decode: true, }, { desc: "ShouldDecodeECDSAP384PrivateKey", have: x509PrivateKeyECDSAP384, want: MustParsePKCS8ECDSAPrivateKey(x509PrivateKeyECDSAP384), decode: true, }, { desc: "ShouldDecodeECDSAP521PrivateKey", have: x509PrivateKeyECDSAP521, want: MustParsePKCS8ECDSAPrivateKey(x509PrivateKeyECDSAP521), decode: true, }, { desc: "ShouldDecodeEd25519PrivateKey", have: x509PrivateKeyEd25519, want: MustParsePKCS8Ed25519PrivateKey(x509PrivateKeyEd25519), decode: true, }, { desc: "ShouldNotDecodeToECDSAPrivateKey", have: x509PrivateKeyRSA2048, want: &ecdsa.PrivateKey{}, decode: true, err: "could not decode to a *ecdsa.PrivateKey: the data is for a *rsa.PrivateKey not a *ecdsa.PrivateKey", }, { desc: "ShouldNotDecodeEmptyRSAKey", have: "", want: nilRSA, decode: true, }, { desc: "ShouldNotDecodeEmptyECDSAKey", have: "", want: nilECDSA, decode: true, }, { desc: "ShouldNotDecodeECDSAKeyToRSAKey", have: x509PrivateKeyECDSAP521, want: nilRSA, decode: true, err: "could not decode to a *rsa.PrivateKey: the data is for a *ecdsa.PrivateKey not a *rsa.PrivateKey", }, { desc: "ShouldNotDecodeRSAKeyToECDSAKey", have: x509PrivateKeyRSA2048, want: nilECDSA, decode: true, err: "could not decode to a *ecdsa.PrivateKey: the data is for a *rsa.PrivateKey not a *ecdsa.PrivateKey", }, { desc: "ShouldNotDecodeBadRSAPrivateKey", have: x509PrivateKeyRSABad, want: nilRSA, decode: true, err: "could not decode to a *rsa.PrivateKey: error occurred attempting to parse PEM block: either no PEM block was supplied or it was malformed", }, { desc: "ShouldNotDecodeBadRSAPrivateKeyTrailingData", have: strings.ReplaceAll(x509PrivateKeyRSA2048, "END PRIVATE KEY-----", "END PRIVATE KEY---------"), want: nilRSA, decode: true, err: "could not decode to a *rsa.PrivateKey: error occurred attempting to parse PEM block: either no PEM block was supplied or it was malformed", }, { desc: "ShouldNotDecodeBadRSAPrivateKeyTrailingDataDoubled", have: x509PrivateKeyRSA2048 + x509PrivateKeyRSA2048, want: nilRSA, decode: true, err: "could not decode to a *rsa.PrivateKey: error occurred attempting to parse PEM block: the block either had trailing data or was otherwise malformed", }, { desc: "ShouldNotDecodeBadECDSAPrivateKey", have: x509PrivateKeyECBad, want: nilECDSA, decode: true, err: "could not decode to a *ecdsa.PrivateKey: error occurred attempting to parse PEM block: either no PEM block was supplied or it was malformed", }, { desc: "ShouldNotDecodeCertificateToRSAPrivateKey", have: x509CertificateRSA2048, want: nilRSA, decode: true, err: "could not decode to a *rsa.PrivateKey: the data is for a *x509.Certificate not a *rsa.PrivateKey", }, { desc: "ShouldNotDecodeCertificateToECDSAPrivateKey", have: x509CertificateRSA2048, want: nilECDSA, decode: true, err: "could not decode to a *ecdsa.PrivateKey: the data is for a *x509.Certificate not a *ecdsa.PrivateKey", }, { desc: "ShouldNotDecodeRSAKeyToCertificate", have: x509PrivateKeyRSA2048, want: nilCert, decode: false, }, { desc: "ShouldNotDecodeECDSAKeyToCertificate", have: x509PrivateKeyECDSAP521, want: nilCert, decode: false, }, } hook := configuration.StringToPrivateKeyHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestStringToX509CertificateHookFunc(t *testing.T) { var nilkey *x509.Certificate testCases := []struct { desc string have any want any err string decode bool }{ { desc: "ShouldDecodeRSACertificate", have: x509CertificateRSA2048, want: MustParseX509Certificate(x509CertificateRSA2048), decode: true, }, { desc: "ShouldDecodeECDSACertificate", have: x509CertificateECDSAP521, want: MustParseX509Certificate(x509CertificateECDSAP521), decode: true, }, { desc: "ShouldDecodeEd25519Certificate", have: x509CertificateEd25519, want: MustParseX509Certificate(x509CertificateEd25519), decode: true, }, { desc: "ShouldDecodeRSACACertificate", have: x509CACertificateRSA2048, want: MustParseX509Certificate(x509CACertificateRSA2048), decode: true, }, { desc: "ShouldDecodeECDSACACertificate", have: x509CACertificateECDSAP521, want: MustParseX509Certificate(x509CACertificateECDSAP521), decode: true, }, { desc: "ShouldDecodeEd25519CACertificate", have: x509CACertificateEd25519, want: MustParseX509Certificate(x509CACertificateEd25519), decode: true, }, { desc: "ShouldDecodeEmptyCertificateToNil", have: "", want: nilkey, decode: true, }, { desc: "ShouldNotDecodeECDSAKeyToCertificate", have: x509PrivateKeyECDSAP224, want: nilkey, decode: true, err: "could not decode to a *x509.Certificate: the data is for a *ecdsa.PrivateKey not a *x509.Certificate", }, { desc: "ShouldNotDecodeBadRSAPrivateKeyToCertificate", have: x509PrivateKeyRSABad, want: nilkey, decode: true, err: "could not decode to a *x509.Certificate: error occurred attempting to parse PEM block: either no PEM block was supplied or it was malformed", }, } hook := configuration.StringToX509CertificateHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, result) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.want, result) default: assert.EqualError(t, err, tc.err) assert.Nil(t, result) } }) } } func TestStringToPasswordDigestHookFunc(t *testing.T) { var nilvalue *schema.PasswordDigest testCases := []struct { name string have any expected any err string decode bool }{ { "ShouldParse", "$plaintext$example", MustParsePasswordDigest("$plaintext$example"), "", true, }, { "ShouldParsePtr", "$plaintext$example", MustParsePasswordDigestPtr("$plaintext$example"), "", true, }, { "ShouldNotParseUnknown", "$abc$example", schema.PasswordDigest{}, "could not decode '$abc$example' to a schema.PasswordDigest: provided encoded hash has an invalid identifier: the identifier 'abc' is unknown to the decoder", false, }, { "ShouldNotParseWrongType", "$abc$example", schema.TLSVersion{}, "", false, }, { "ShouldNotParseWrongTypePtr", "$abc$example", &schema.TLSVersion{}, "", false, }, { "ShouldNotParseEmptyString", "", schema.PasswordDigest{}, "could not decode an empty value to a schema.PasswordDigest: must have a non-empty value", false, }, { "ShouldParseEmptyStringPtr", "", nilvalue, "", true, }, } hook := configuration.StringToPasswordDigestHookFunc() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have) if tc.err != "" { assert.EqualError(t, err, tc.err) if !tc.decode { assert.Nil(t, actual) } } else { assert.NoError(t, err) if tc.decode { assert.Equal(t, tc.expected, actual) } else { assert.Equal(t, tc.have, actual) } } }) } } func TestStringToTLSVersionHookFunc(t *testing.T) { testCases := []struct { name string have any expected any err string decode bool }{ { "ShouldParseTLS1.3", "TLS1.3", schema.TLSVersion{Value: tls.VersionTLS13}, "", true, }, { "ShouldParseTLS1.3PTR", "TLS1.3", &schema.TLSVersion{Value: tls.VersionTLS13}, "", true, }, { "ShouldParseTLS1.2", "TLS1.2", schema.TLSVersion{Value: tls.VersionTLS12}, "", true, }, { "ShouldNotParseInt", 1, &schema.TLSVersion{}, "", false, }, { "ShouldNotParseNonVersion", "1", &schema.TLSVersion{}, "could not decode '1' to a *schema.TLSVersion: supplied tls version isn't supported", false, }, } hook := configuration.StringToTLSVersionHookFunc() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have) if tc.err != "" { assert.EqualError(t, err, tc.err) if !tc.decode { assert.Nil(t, actual) } } else { assert.NoError(t, err) if tc.decode { assert.Equal(t, tc.expected, actual) } else { assert.Equal(t, tc.have, actual) } } }) } } func TestStringToX509CertificateChainHookFunc(t *testing.T) { var nilkey *schema.X509CertificateChain testCases := []struct { desc string have any expected any err, verr string decode bool }{ { desc: "ShouldDecodeRSA1024Certificate", have: x509CertificateRSA1024, expected: MustParseX509CertificateChain(x509CertificateRSA1024), decode: true, }, { desc: "ShouldDecodeRSA1024CertificateNoPtr", have: x509CertificateRSA1024, expected: *MustParseX509CertificateChain(x509CertificateRSA1024), decode: true, }, { desc: "ShouldDecodeRSA1024CACertificate", have: x509CACertificateRSA1024, expected: MustParseX509CertificateChain(x509CACertificateRSA1024), decode: true, }, { desc: "ShouldDecodeRSA1024CACertificateNoPtr", have: x509CACertificateRSA1024, expected: *MustParseX509CertificateChain(x509CACertificateRSA1024), decode: true, }, { desc: "ShouldDecodeRSA1024CertificateChain", have: BuildChain(x509CertificateRSA1024, x509CACertificateRSA1024), expected: MustParseX509CertificateChain(x509CertificateRSA1024, x509CACertificateRSA1024), decode: true, }, { desc: "ShouldDecodeRSA1024CertificateChainNoPtr", have: BuildChain(x509CertificateRSA1024, x509CACertificateRSA1024), expected: *MustParseX509CertificateChain(x509CertificateRSA1024, x509CACertificateRSA1024), decode: true, }, { desc: "ShouldDecodeRSA2048Certificate", have: x509CertificateRSA2048, expected: MustParseX509CertificateChain(x509CertificateRSA2048), decode: true, }, { desc: "ShouldDecodeRSA2048CertificateNoPtr", have: x509CertificateRSA2048, expected: *MustParseX509CertificateChain(x509CertificateRSA2048), decode: true, }, { desc: "ShouldDecodeRSA2048CACertificate", have: x509CACertificateRSA2048, expected: MustParseX509CertificateChain(x509CACertificateRSA2048), decode: true, }, { desc: "ShouldDecodeRSA2048CACertificateNoPtr", have: x509CACertificateRSA2048, expected: *MustParseX509CertificateChain(x509CACertificateRSA2048), decode: true, }, { desc: "ShouldDecodeRSA2048CertificateChain", have: BuildChain(x509CertificateRSA2048, x509CACertificateRSA2048), expected: MustParseX509CertificateChain(x509CertificateRSA2048, x509CACertificateRSA2048), decode: true, }, { desc: "ShouldDecodeRSA2048CertificateChainNoPtr", have: BuildChain(x509CertificateRSA2048, x509CACertificateRSA2048), expected: *MustParseX509CertificateChain(x509CertificateRSA2048, x509CACertificateRSA2048), decode: true, }, { desc: "ShouldDecodeRSA4096Certificate", have: x509CertificateRSA4096, expected: MustParseX509CertificateChain(x509CertificateRSA4096), decode: true, }, { desc: "ShouldDecodeRSA4096CertificateNoPtr", have: x509CertificateRSA4096, expected: *MustParseX509CertificateChain(x509CertificateRSA4096), decode: true, }, { desc: "ShouldDecodeRSA4096CACertificate", have: x509CACertificateRSA4096, expected: MustParseX509CertificateChain(x509CACertificateRSA4096), decode: true, }, { desc: "ShouldDecodeRSA4096CACertificateNoPtr", have: x509CACertificateRSA4096, expected: *MustParseX509CertificateChain(x509CACertificateRSA4096), decode: true, }, { desc: "ShouldDecodeRSA4096CertificateChain", have: BuildChain(x509CertificateRSA4096, x509CACertificateRSA4096), expected: MustParseX509CertificateChain(x509CertificateRSA4096, x509CACertificateRSA4096), decode: true, }, { desc: "ShouldDecodeRSA4096CertificateChainNoPtr", have: BuildChain(x509CertificateRSA4096, x509CACertificateRSA4096), expected: *MustParseX509CertificateChain(x509CertificateRSA4096, x509CACertificateRSA4096), decode: true, }, { desc: "ShouldDecodeECDSAP224Certificate", have: x509CertificateECDSAP224, expected: MustParseX509CertificateChain(x509CertificateECDSAP224), decode: true, }, { desc: "ShouldDecodeECDSAP224CertificateNoPtr", have: x509CertificateECDSAP224, expected: *MustParseX509CertificateChain(x509CertificateECDSAP224), decode: true, }, { desc: "ShouldDecodeECDSAP224CACertificate", have: x509CACertificateECDSAP224, expected: MustParseX509CertificateChain(x509CACertificateECDSAP224), decode: true, }, { desc: "ShouldDecodeECDSAP224CACertificateNoPtr", have: x509CACertificateECDSAP224, expected: *MustParseX509CertificateChain(x509CACertificateECDSAP224), decode: true, }, { desc: "ShouldDecodeECDSAP224CertificateChain", have: BuildChain(x509CertificateECDSAP224, x509CACertificateECDSAP224), expected: MustParseX509CertificateChain(x509CertificateECDSAP224, x509CACertificateECDSAP224), decode: true, }, { desc: "ShouldDecodeECDSAP224CertificateChainNoPtr", have: BuildChain(x509CertificateECDSAP224, x509CACertificateECDSAP224), expected: *MustParseX509CertificateChain(x509CertificateECDSAP224, x509CACertificateECDSAP224), decode: true, }, { desc: "ShouldDecodeECDSAP256Certificate", have: x509CertificateECDSAP256, expected: MustParseX509CertificateChain(x509CertificateECDSAP256), decode: true, }, { desc: "ShouldDecodeECDSAP256CertificateNoPtr", have: x509CertificateECDSAP256, expected: *MustParseX509CertificateChain(x509CertificateECDSAP256), decode: true, }, { desc: "ShouldDecodeECDSAP256CACertificate", have: x509CACertificateECDSAP256, expected: MustParseX509CertificateChain(x509CACertificateECDSAP256), decode: true, }, { desc: "ShouldDecodeECDSAP256CACertificateNoPtr", have: x509CACertificateECDSAP256, expected: *MustParseX509CertificateChain(x509CACertificateECDSAP256), decode: true, }, { desc: "ShouldDecodeECDSAP256CertificateChain", have: BuildChain(x509CertificateECDSAP256, x509CACertificateECDSAP256), expected: MustParseX509CertificateChain(x509CertificateECDSAP256, x509CACertificateECDSAP256), decode: true, }, { desc: "ShouldDecodeECDSAP256CertificateChainNoPtr", have: BuildChain(x509CertificateECDSAP256, x509CACertificateECDSAP256), expected: *MustParseX509CertificateChain(x509CertificateECDSAP256, x509CACertificateECDSAP256), decode: true, }, { desc: "ShouldDecodeECDSAP384Certificate", have: x509CertificateECDSAP384, expected: MustParseX509CertificateChain(x509CertificateECDSAP384), decode: true, }, { desc: "ShouldDecodeECDSAP384CertificateNoPtr", have: x509CertificateECDSAP384, expected: *MustParseX509CertificateChain(x509CertificateECDSAP384), decode: true, }, { desc: "ShouldDecodeECDSAP384CACertificate", have: x509CACertificateECDSAP384, expected: MustParseX509CertificateChain(x509CACertificateECDSAP384), decode: true, }, { desc: "ShouldDecodeECDSAP384CACertificateNoPtr", have: x509CACertificateECDSAP384, expected: *MustParseX509CertificateChain(x509CACertificateECDSAP384), decode: true, }, { desc: "ShouldDecodeECDSAP384CertificateChain", have: BuildChain(x509CertificateECDSAP384, x509CACertificateECDSAP384), expected: MustParseX509CertificateChain(x509CertificateECDSAP384, x509CACertificateECDSAP384), decode: true, }, { desc: "ShouldDecodeECDSAP384CertificateChainNoPtr", have: BuildChain(x509CertificateECDSAP384, x509CACertificateECDSAP384), expected: *MustParseX509CertificateChain(x509CertificateECDSAP384, x509CACertificateECDSAP384), decode: true, }, { desc: "ShouldDecodeECDSAP521Certificate", have: x509CertificateECDSAP521, expected: MustParseX509CertificateChain(x509CertificateECDSAP521), decode: true, }, { desc: "ShouldDecodeECDSAP521CertificateNoPtr", have: x509CertificateECDSAP521, expected: *MustParseX509CertificateChain(x509CertificateECDSAP521), decode: true, }, { desc: "ShouldDecodeECDSAP521CACertificate", have: x509CACertificateECDSAP521, expected: MustParseX509CertificateChain(x509CACertificateECDSAP521), decode: true, }, { desc: "ShouldDecodeECDSAP521CACertificateNoPtr", have: x509CACertificateECDSAP521, expected: *MustParseX509CertificateChain(x509CACertificateECDSAP521), decode: true, }, { desc: "ShouldDecodeECDSAP521CertificateChain", have: BuildChain(x509CertificateECDSAP521, x509CACertificateECDSAP521), expected: MustParseX509CertificateChain(x509CertificateECDSAP521, x509CACertificateECDSAP521), decode: true, }, { desc: "ShouldDecodeECDSAP521CertificateChainNoPtr", have: BuildChain(x509CertificateECDSAP521, x509CACertificateECDSAP521), expected: *MustParseX509CertificateChain(x509CertificateECDSAP521, x509CACertificateECDSAP521), decode: true, }, { desc: "ShouldNotDecodeBadRSACertificateChain", have: BuildChain(x509CertificateRSA2048, x509CACertificateECDSAP521), expected: MustParseX509CertificateChain(x509CertificateRSA2048, x509CACertificateECDSAP521), verr: "certificate #1 in chain is not signed properly by certificate #2 in chain: x509: signature algorithm specifies an RSA public key, but have public key of type *ecdsa.PublicKey", decode: true, }, { desc: "ShouldDecodeEmptyCertificateToNil", have: "", expected: nilkey, decode: true, }, { desc: "ShouldDecodeEmptyCertificateToEmptyStruct", have: "", expected: schema.X509CertificateChain{}, decode: true, }, { desc: "ShouldNotDecodeECDSAKeyToCertificate", have: x509PrivateKeyECDSAP224, expected: nilkey, decode: true, err: "could not decode to a *schema.X509CertificateChain: the PEM data chain contains a PRIVATE KEY but only certificates are expected", }, { desc: "ShouldNotDecodeBadRSAPrivateKeyToCertificate", have: x509PrivateKeyRSABad, expected: nilkey, decode: true, err: "could not decode to a *schema.X509CertificateChain: invalid PEM block", }, } hook := configuration.StringToX509CertificateChainHookFunc() for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, actual) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.expected, actual) if tc.expected == nilkey { break } switch chain := actual.(type) { case *schema.X509CertificateChain: require.NotNil(t, chain) if tc.verr == "" { assert.NoError(t, chain.Validate()) } else { assert.EqualError(t, chain.Validate(), tc.verr) } case schema.X509CertificateChain: require.NotNil(t, chain) if tc.verr == "" { assert.NoError(t, chain.Validate()) } else { assert.EqualError(t, chain.Validate(), tc.verr) } } default: assert.EqualError(t, err, tc.err) assert.Nil(t, actual) } }) } } func TestStringToUUIDHookFunc(t *testing.T) { var nilkey *uuid.UUID toPtr := func(value uuid.UUID) *uuid.UUID { return &value } testCases := []struct { name string have any expected any err, verr string decode bool }{ { name: "ShouldNotDecodeEmptyString", have: "", expected: uuid.UUID{}, decode: true, err: "could not decode an empty value to a uuid.UUID: must have a non-empty value", }, { name: "ShouldDecodeEmptyStringNil", have: "", expected: nilkey, decode: true, err: "", }, { name: "ShouldDecodeValid", have: "cb69481e-8ff7-4039-93ec-0a2729a154a8", expected: uuid.MustParse("cb69481e-8ff7-4039-93ec-0a2729a154a8"), decode: true, err: "", }, { name: "ShouldDecodeValidPtr", have: "cb69481e-8ff7-4039-93ec-0a2729a154a8", expected: toPtr(uuid.MustParse("cb69481e-8ff7-4039-93ec-0a2729a154a8")), decode: true, err: "", }, { name: "ShouldNotDecodeParseError", have: "cb69481e-4039-93ec-0a2729a154a8", expected: uuid.UUID{}, decode: true, err: "could not decode 'cb69481e-4039-93ec-0a2729a154a8' to a uuid.UUID: invalid UUID length: 31", }, { name: "ShouldNotDecodeParseErrorPtr", have: "cb69481e-4039-93ec-0a2729a154a8", expected: nilkey, decode: true, err: "could not decode 'cb69481e-4039-93ec-0a2729a154a8' to a *uuid.UUID: invalid UUID length: 31", }, } hook := configuration.StringToUUIDHookFunc() for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have) switch { case !tc.decode: assert.NoError(t, err) assert.Equal(t, tc.have, actual) case tc.err == "": assert.NoError(t, err) require.Equal(t, tc.expected, actual) default: assert.EqualError(t, err, tc.err) assert.Nil(t, actual) } }) } } func TestStringToIPNetworksHookFunc(t *testing.T) { mustParseNet := func(in string) *net.IPNet { _, n, err := net.ParseCIDR(in) if err != nil { panic(err) } return n } testCases := []struct { name string path string have *schema.Definitions expected TestConfigDefinitions err string }{ { "ShouldDecode", "decode_networks.yml", &schema.Definitions{}, TestConfigDefinitions{ Definitions: schema.Definitions{ Network: map[string][]*net.IPNet{ "single": { mustParseNet("192.168.0.1/32"), }, "example": { mustParseNet("192.168.1.20/32"), mustParseNet("192.168.2.0/24"), }, }, }, }, "", }, { "ShouldDecodeDefinitions", "decode_networks_abc.yml", &schema.Definitions{ Network: map[string][]*net.IPNet{ "abc": { mustParseNet("1.1.1.1/32"), mustParseNet("1.1.1.2/32"), }, }, }, TestConfigDefinitions{ Definitions: schema.Definitions{ Network: map[string][]*net.IPNet{ "example": { mustParseNet("192.168.1.20/32"), mustParseNet("192.168.2.0/24"), mustParseNet("1.1.1.1/32"), mustParseNet("1.1.1.2/32"), }, }, }, }, "", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { result := TestConfigDefinitions{} val := schema.NewStructValidator() _, err := configuration.LoadAdvanced(val, "", &result, tc.have, configuration.NewDefaultSourcesFiltered([]string{path.Join("./test_resources", tc.path)}, nil, configuration.DefaultEnvPrefix, configuration.DefaultEnvDelimiter)...) if tc.err == "" { assert.NoError(t, err) assert.Equal(t, tc.expected, result) assert.Len(t, val.Errors(), 0) } else { assert.EqualError(t, err, tc.err) } }) } } type TestConfigDefinitions struct { Definitions schema.Definitions `koanf:"definitions"` } var ( //nolint:gosec x509PrivateKeyRSABad = ` -----BEGIN RSA PRIVATE KEY----- bad key -----END RSA PRIVATE KEY-----` //nolint:gosec x509PrivateKeyECBad = ` -----BEGIN EC PRIVATE KEY----- bad key -----END EC PRIVATE KEY-----` ) func MustParsePKCS8RSAPrivateKey(data string) *rsa.PrivateKey { return MustParsePKCS8PrivateKey(data).(*rsa.PrivateKey) } func MustParsePKCS8ECDSAPrivateKey(data string) *ecdsa.PrivateKey { return MustParsePKCS8PrivateKey(data).(*ecdsa.PrivateKey) } func MustParsePKCS8Ed25519PrivateKey(data string) *ed25519.PrivateKey { key := MustParsePKCS8PrivateKey(data).(ed25519.PrivateKey) return &key } func MustParsePKCS8PrivateKey(data string) schema.CryptographicPrivateKey { block, _ := pem.Decode([]byte(data)) if block == nil || block.Bytes == nil || len(block.Bytes) == 0 { panic("not pem encoded") } if block.Type != "PRIVATE KEY" { panic("not PKCS8 private key") } key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { panic(err) } if pkey, ok := key.(schema.CryptographicPrivateKey); ok { return pkey } panic("key does not implement the required members") } func MustParseX509Certificate(data string) *x509.Certificate { block, _ := pem.Decode([]byte(data)) if block == nil || len(block.Bytes) == 0 { panic("not a PEM") } cert, err := x509.ParseCertificate(block.Bytes) if err != nil { panic(err) } return cert } func BuildChain(pems ...string) string { buf := bytes.Buffer{} for i, data := range pems { if i != 0 { buf.WriteString("\n") } buf.WriteString(data) } return buf.String() } func MustParseX509CertificateChain(datas ...string) *schema.X509CertificateChain { chain, err := schema.NewX509CertificateChain(BuildChain(datas...)) if err != nil { panic(err) } return chain } func testInt32Ptr(i int32) *int32 { return &i } func testTimeDurationPtr(t time.Duration) *time.Duration { return &t } func testRefreshIntervalDurationPtr(t time.Duration) *schema.RefreshIntervalDuration { x := schema.NewRefreshIntervalDuration(t) return &x } var ( testTrue = true testZero int32 testString = "" ) func MustParseAddress(input string) schema.Address { address, err := schema.NewAddress(input) if err != nil { panic(err) } addr := *address return addr } func MustParseAddressPtr(input string) *schema.Address { address, err := schema.NewAddress(input) if err != nil { panic(err) } return address } func MustParsePasswordDigest(input string) schema.PasswordDigest { digest, err := schema.DecodePasswordDigest(input) if err != nil { panic(err) } return *digest } func MustParsePasswordDigestPtr(input string) *schema.PasswordDigest { digest, err := schema.DecodePasswordDigest(input) if err != nil { panic(err) } return digest } const ( pathCrypto = "./test_resources/crypto/%s.%s" ) func MustLoadCryptoSet(alg string, legacy bool, extra ...string) (certCA, keyCA, cert, key string) { extraAlt := make([]string, len(extra)) copy(extraAlt, extra) if legacy { extraAlt = append(extraAlt, "legacy") } return MustLoadCryptoRaw(true, alg, "crt", extra...), MustLoadCryptoRaw(true, alg, "pem", extra...), MustLoadCryptoRaw(false, alg, "crt", extraAlt...), MustLoadCryptoRaw(false, alg, "pem", extraAlt...) } func MustLoadCryptoRaw(ca bool, alg, ext string, extra ...string) string { var fparts []string if ca { fparts = append(fparts, "ca") } fparts = append(fparts, strings.ToLower(alg)) if len(extra) != 0 { fparts = append(fparts, extra...) } var ( data []byte err error ) if data, err = os.ReadFile(fmt.Sprintf(pathCrypto, strings.Join(fparts, "."), ext)); err != nil { panic(err) } return string(data) } var ( x509CertificateRSA1024, x509CertificateRSA2048, x509CertificateRSA4096, x509CertificateEd25519 string x509CACertificateRSA1024, x509CACertificateRSA2048, x509CACertificateRSA4096, x509CACertificateEd25519 string x509PrivateKeyRSA1024, x509PrivateKeyRSA2048, x509PrivateKeyRSA4096, x509PrivateKeyEd25519 string x509CAPrivateKeyRSA1024, x509CAPrivateKeyRSA2048, x509CAPrivateKeyRSA4096, x509CAPrivateKeyEd25519 string x509CertificateECDSAP224, x509CertificateECDSAP256, x509CertificateECDSAP384, x509CertificateECDSAP521 string x509CACertificateECDSAP224, x509CACertificateECDSAP256, x509CACertificateECDSAP384, x509CACertificateECDSAP521 string x509PrivateKeyECDSAP224, x509PrivateKeyECDSAP256, x509PrivateKeyECDSAP384, x509PrivateKeyECDSAP521 string x509CAPrivateKeyECDSAP224, x509CAPrivateKeyECDSAP256, x509CAPrivateKeyECDSAP384, x509CAPrivateKeyECDSAP521 string ) func init() { x509CACertificateRSA1024, x509CAPrivateKeyRSA1024, x509CertificateRSA1024, x509PrivateKeyRSA1024 = MustLoadCryptoSet("RSA", false, "1024") x509CACertificateRSA2048, x509CAPrivateKeyRSA2048, x509CertificateRSA2048, x509PrivateKeyRSA2048 = MustLoadCryptoSet("RSA", false, "2048") x509CACertificateRSA4096, x509CAPrivateKeyRSA4096, x509CertificateRSA4096, x509PrivateKeyRSA4096 = MustLoadCryptoSet("RSA", false, "4096") x509CACertificateECDSAP224, x509CAPrivateKeyECDSAP224, x509CertificateECDSAP224, x509PrivateKeyECDSAP224 = MustLoadCryptoSet("ECDSA", false, "P224") x509CACertificateECDSAP256, x509CAPrivateKeyECDSAP256, x509CertificateECDSAP256, x509PrivateKeyECDSAP256 = MustLoadCryptoSet("ECDSA", false, "P256") x509CACertificateECDSAP384, x509CAPrivateKeyECDSAP384, x509CertificateECDSAP384, x509PrivateKeyECDSAP384 = MustLoadCryptoSet("ECDSA", false, "P384") x509CACertificateECDSAP521, x509CAPrivateKeyECDSAP521, x509CertificateECDSAP521, x509PrivateKeyECDSAP521 = MustLoadCryptoSet("ECDSA", false, "P521") x509CACertificateEd25519, x509CAPrivateKeyEd25519, x509CertificateEd25519, x509PrivateKeyEd25519 = MustLoadCryptoSet("Ed25519", false) }