fix(configuration): fail to parse large int duration (#5408)
Large integers used with the duration common syntax failed to parse if they exceeded the ability to fit into an int32. Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/5406/head
parent
3abad065a3
commit
713f8e9ab7
|
@ -2,6 +2,8 @@ package configuration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultEnvPrefix is the default environment prefix.
|
// DefaultEnvPrefix is the default environment prefix.
|
||||||
|
@ -38,6 +40,10 @@ const (
|
||||||
errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s%s: %w"
|
errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s%s: %w"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
durationMax = time.Duration(math.MaxInt64)
|
||||||
|
)
|
||||||
|
|
||||||
// IMPORTANT: There is an uppercase copy of this in github.com/authelia/authelia/internal/templates named
|
// IMPORTANT: There is an uppercase copy of this in github.com/authelia/authelia/internal/templates named
|
||||||
// envSecretSuffixes.
|
// envSecretSuffixes.
|
||||||
// Make sure you update these at the same time.
|
// Make sure you update these at the same time.
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -112,19 +113,14 @@ func StringToURLHookFunc() mapstructure.DecodeHookFuncType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToTimeDurationHookFunc converts string and integer types to a time.Duration.
|
// ToTimeDurationHookFunc converts string and integer types to a time.Duration.
|
||||||
|
//
|
||||||
|
//nolint:gocyclo // Function is necessarily complex though flows well due to switch statement usage.
|
||||||
func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
|
func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
|
||||||
return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
|
return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
|
||||||
var ptr bool
|
var (
|
||||||
|
ptr bool
|
||||||
switch f.Kind() {
|
prefixType string
|
||||||
case reflect.String, reflect.Int, reflect.Int32, reflect.Int64:
|
)
|
||||||
// We only allow string and integer from kinds to match.
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
prefixType := ""
|
|
||||||
|
|
||||||
if t.Kind() == reflect.Ptr {
|
if t.Kind() == reflect.Ptr {
|
||||||
ptr = true
|
ptr = true
|
||||||
|
@ -139,6 +135,14 @@ func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch f.Kind() {
|
||||||
|
case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64:
|
||||||
|
// We only allow string and integer from kinds to match.
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
var result time.Duration
|
var result time.Duration
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -151,11 +155,29 @@ func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
|
||||||
case f.Kind() == reflect.Int:
|
case f.Kind() == reflect.Int:
|
||||||
seconds := data.(int)
|
seconds := data.(int)
|
||||||
|
|
||||||
|
result = time.Second * time.Duration(seconds)
|
||||||
|
case f.Kind() == reflect.Int8:
|
||||||
|
seconds := data.(int8)
|
||||||
|
|
||||||
|
result = time.Second * time.Duration(seconds)
|
||||||
|
case f.Kind() == reflect.Int16:
|
||||||
|
seconds := data.(int16)
|
||||||
|
|
||||||
result = time.Second * time.Duration(seconds)
|
result = time.Second * time.Duration(seconds)
|
||||||
case f.Kind() == reflect.Int32:
|
case f.Kind() == reflect.Int32:
|
||||||
seconds := data.(int32)
|
seconds := data.(int32)
|
||||||
|
|
||||||
result = time.Second * time.Duration(seconds)
|
result = time.Second * time.Duration(seconds)
|
||||||
|
case f.Kind() == reflect.Float64:
|
||||||
|
fseconds := data.(float64)
|
||||||
|
|
||||||
|
if fseconds > durationMax.Seconds() {
|
||||||
|
result = durationMax
|
||||||
|
} else {
|
||||||
|
seconds, _ := strconv.Atoi(fmt.Sprintf("%.0f", fseconds))
|
||||||
|
|
||||||
|
result = time.Second * time.Duration(seconds)
|
||||||
|
}
|
||||||
case f == expectedType:
|
case f == expectedType:
|
||||||
result = data.(time.Duration)
|
result = data.(time.Duration)
|
||||||
case f.Kind() == reflect.Int64:
|
case f.Kind() == reflect.Int64:
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"math"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -345,12 +346,36 @@ func TestToTimeDurationHookFunc(t *testing.T) {
|
||||||
want: time.Second * 60,
|
want: time.Second * 60,
|
||||||
decode: true,
|
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",
|
desc: "ShouldDecodeInt32ToSeconds",
|
||||||
have: int32(90),
|
have: int32(90),
|
||||||
want: time.Second * 90,
|
want: time.Second * 90,
|
||||||
decode: true,
|
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",
|
desc: "ShouldDecodeInt64ToSeconds",
|
||||||
have: int64(120),
|
have: int64(120),
|
||||||
|
@ -375,6 +400,12 @@ func TestToTimeDurationHookFunc(t *testing.T) {
|
||||||
want: time.Duration(0),
|
want: time.Duration(0),
|
||||||
decode: true,
|
decode: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "ShouldSkipParsingBoolean",
|
||||||
|
have: true,
|
||||||
|
want: time.Duration(0),
|
||||||
|
decode: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldNotDecodeFromBool",
|
desc: "ShouldNotDecodeFromBool",
|
||||||
have: true,
|
have: true,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -86,6 +87,18 @@ func TestShouldHaveNotifier(t *testing.T) {
|
||||||
assert.NotNil(t, config.Notifier)
|
assert.NotNil(t, config.Notifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldParseLargeIntegerDurations(t *testing.T) {
|
||||||
|
val := schema.NewStructValidator()
|
||||||
|
_, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config.durations.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, val.Errors(), 0)
|
||||||
|
assert.Len(t, val.Warnings(), 0)
|
||||||
|
|
||||||
|
assert.Equal(t, durationMax, config.Regulation.FindTime)
|
||||||
|
assert.Equal(t, time.Second*1000, config.Regulation.BanTime)
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldValidateConfigurationWithEnv(t *testing.T) {
|
func TestShouldValidateConfigurationWithEnv(t *testing.T) {
|
||||||
testSetEnv(t, "SESSION_SECRET", "abc")
|
testSetEnv(t, "SESSION_SECRET", "abc")
|
||||||
testSetEnv(t, "STORAGE_MYSQL_PASSWORD", "abc")
|
testSetEnv(t, "STORAGE_MYSQL_PASSWORD", "abc")
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
---
|
||||||
|
default_redirection_url: 'https://home.example.com:8080/'
|
||||||
|
|
||||||
|
server:
|
||||||
|
address: 'tcp://127.0.0.1:9091'
|
||||||
|
endpoints:
|
||||||
|
authz:
|
||||||
|
forward-auth:
|
||||||
|
implementation: 'ForwardAuth'
|
||||||
|
authn_strategies:
|
||||||
|
- name: 'HeaderProxyAuthorization'
|
||||||
|
- name: 'CookieSession'
|
||||||
|
ext-authz:
|
||||||
|
implementation: 'ExtAuthz'
|
||||||
|
authn_strategies:
|
||||||
|
- name: 'HeaderProxyAuthorization'
|
||||||
|
- name: 'CookieSession'
|
||||||
|
auth-request:
|
||||||
|
implementation: 'AuthRequest'
|
||||||
|
authn_strategies:
|
||||||
|
- name: 'HeaderAuthRequestProxyAuthorization'
|
||||||
|
- name: 'CookieSession'
|
||||||
|
legacy:
|
||||||
|
implementation: 'Legacy'
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: 'debug'
|
||||||
|
|
||||||
|
totp:
|
||||||
|
issuer: 'authelia.com'
|
||||||
|
|
||||||
|
duo_api:
|
||||||
|
hostname: 'api-123456789.example.com'
|
||||||
|
integration_key: 'ABCDEF'
|
||||||
|
|
||||||
|
authentication_backend:
|
||||||
|
ldap:
|
||||||
|
address: 'ldap://127.0.0.1'
|
||||||
|
tls:
|
||||||
|
private_key: |
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
|
||||||
|
cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
|
||||||
|
ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
|
||||||
|
Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
|
||||||
|
rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
|
||||||
|
EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
|
||||||
|
L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
|
||||||
|
Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
|
||||||
|
9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
|
||||||
|
8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
|
||||||
|
I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
|
||||||
|
CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
|
||||||
|
hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
|
||||||
|
jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
|
||||||
|
E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
|
||||||
|
CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
|
||||||
|
jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
|
||||||
|
Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
|
||||||
|
Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
|
||||||
|
bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
|
||||||
|
otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
|
||||||
|
HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
|
||||||
|
tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
|
||||||
|
USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
|
||||||
|
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
base_dn: 'dc=example,dc=com'
|
||||||
|
username_attribute: 'uid'
|
||||||
|
additional_users_dn: 'ou=users'
|
||||||
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
|
additional_groups_dn: 'ou=groups'
|
||||||
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
|
group_name_attribute: 'cn'
|
||||||
|
mail_attribute: 'mail'
|
||||||
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
default_policy: 'deny'
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# Rules applied to everyone
|
||||||
|
- domain: 'public.example.com'
|
||||||
|
policy: 'bypass'
|
||||||
|
|
||||||
|
- domain: 'secure.example.com'
|
||||||
|
policy: 'one_factor'
|
||||||
|
# Network based rule, if not provided any network matches.
|
||||||
|
networks:
|
||||||
|
- '192.168.1.0/24'
|
||||||
|
- domain: 'secure.example.com'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
- domain: ['singlefactor.example.com', 'onefactor.example.com']
|
||||||
|
policy: 'one_factor'
|
||||||
|
|
||||||
|
# Rules applied to 'admins' group
|
||||||
|
- domain: 'mx2.mail.example.com'
|
||||||
|
subject: 'group:admins'
|
||||||
|
policy: 'deny'
|
||||||
|
- domain: '*.example.com'
|
||||||
|
subject: 'group:admins'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/groups/dev/.*$'
|
||||||
|
subject: 'group:dev'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
# Rules applied to user 'john'
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/users/john/.*$'
|
||||||
|
subject: 'user:john'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group and user 'john'
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/deny-all.*$'
|
||||||
|
subject: ['group:dev', 'user:john']
|
||||||
|
policy: 'deny'
|
||||||
|
|
||||||
|
# Rules applied to user 'harry'
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/users/harry/.*$'
|
||||||
|
subject: 'user:harry'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
# Rules applied to user 'bob'
|
||||||
|
- domain: '*.mail.example.com'
|
||||||
|
subject: 'user:bob'
|
||||||
|
policy: 'two_factor'
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/users/bob/.*$'
|
||||||
|
subject: 'user:bob'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
session:
|
||||||
|
name: 'authelia_session'
|
||||||
|
expiration: '1h' # 1 hour
|
||||||
|
inactivity: '5m' # 5 minutes
|
||||||
|
domain: 'example.com'
|
||||||
|
redis:
|
||||||
|
host: '127.0.0.1'
|
||||||
|
port: 6379
|
||||||
|
high_availability:
|
||||||
|
sentinel_name: 'test'
|
||||||
|
|
||||||
|
regulation:
|
||||||
|
max_retries: 3
|
||||||
|
find_time: 123456789123456789123
|
||||||
|
ban_time: 1.0e3
|
||||||
|
|
||||||
|
storage:
|
||||||
|
mysql:
|
||||||
|
address: 'tcp://127.0.0.1:3306'
|
||||||
|
database: 'authelia'
|
||||||
|
username: 'authelia'
|
||||||
|
|
||||||
|
notifier:
|
||||||
|
smtp:
|
||||||
|
address: 'smtp://127.0.0.1:1025'
|
||||||
|
username: 'test'
|
||||||
|
sender: 'admin@example.com'
|
||||||
|
disable_require_tls: true
|
||||||
|
...
|
Loading…
Reference in New Issue