Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions internal/configs/config_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type ConfigParams struct {
MainAppProtectDosLogFormat []string
MainAppProtectDosLogFormatEscaping string
MainAppProtectDosArbFqdn string
OIDC OIDC
ProxyBuffering bool
ProxyBuffers string
ProxyBufferSize string
Expand Down Expand Up @@ -192,6 +193,15 @@ type ZoneSync struct {
ResolverIPV6 *bool
}

// OIDC holds OIDC configuration parameters.
type OIDC struct {
PKCETimeout string
IDTokenTimeout string
AccessTimeout string
RefreshTimeout string
SIDSTimeout string
}

// MGMTSecrets holds mgmt block secret names
type MGMTSecrets struct {
License string
Expand Down Expand Up @@ -258,6 +268,13 @@ func NewDefaultConfigParams(ctx context.Context, isPlus bool) *ConfigParams {
LimitReqZoneSize: "10m",
LimitReqLogLevel: "error",
LimitReqRejectCode: 429,
OIDC: OIDC{
PKCETimeout: "90s",
IDTokenTimeout: "1h",
AccessTimeout: "1h",
RefreshTimeout: "8h",
SIDSTimeout: "8h",
},
}
}

Expand Down
77 changes: 72 additions & 5 deletions internal/configs/configmaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,11 @@ func ParseConfigMap(ctx context.Context, cfgm *v1.ConfigMap, nginxPlus bool, has
configOk = false
}

err = parseConfigMapOIDC(l, cfgm, cfgParams, eventLog)
if err != nil {
configOk = false
}

if upstreamZoneSize, exists := cfgm.Data["upstream-zone-size"]; exists {
cfgParams.UpstreamZoneSize = upstreamZoneSize
}
Expand Down Expand Up @@ -694,6 +699,61 @@ func ParseConfigMap(ctx context.Context, cfgm *v1.ConfigMap, nginxPlus bool, has
return cfgParams, configOk
}

// parseConfigMapOIDC parses OIDC timeout configuration from ConfigMap.
func parseConfigMapOIDC(l *slog.Logger, cfgm *v1.ConfigMap, cfgParams *ConfigParams, eventLog record.EventRecorder) error {
if oidcPKCETimeout, exists := cfgm.Data["oidc-pkce-timeout"]; exists {
pkceTimeout, err := ParseTime(oidcPKCETimeout)
if err != nil {
errorText := fmt.Sprintf("ConfigMap %s/%s: invalid value for 'oidc-pkce-timeout': %q, must be a valid nginx time (e.g. '90s', '5m', '1h')", cfgm.Namespace, cfgm.Name, oidcPKCETimeout)
nl.Warn(l, errorText)
eventLog.Event(cfgm, v1.EventTypeWarning, nl.EventReasonInvalidValue, errorText)
return err
}
cfgParams.OIDC.PKCETimeout = pkceTimeout
}
if oidcIDTokensTimeout, exists := cfgm.Data["oidc-id-tokens-timeout"]; exists {
idTokensTimeout, err := ParseTime(oidcIDTokensTimeout)
if err != nil {
errorText := fmt.Sprintf("ConfigMap %s/%s: invalid value for 'oidc-id-tokens-timeout': %q, must be a valid nginx time (e.g. '1h', '30m', '2h')", cfgm.Namespace, cfgm.Name, oidcIDTokensTimeout)
nl.Warn(l, errorText)
eventLog.Event(cfgm, v1.EventTypeWarning, nl.EventReasonInvalidValue, errorText)
return err
}
cfgParams.OIDC.IDTokenTimeout = idTokensTimeout
}
if oidcAccessTokensTimeout, exists := cfgm.Data["oidc-access-tokens-timeout"]; exists {
accessTokensTimeout, err := ParseTime(oidcAccessTokensTimeout)
if err != nil {
errorText := fmt.Sprintf("ConfigMap %s/%s: invalid value for 'oidc-access-tokens-timeout': %q, must be a valid nginx time (e.g. '1h', '30m', '2h')", cfgm.Namespace, cfgm.Name, oidcAccessTokensTimeout)
nl.Warn(l, errorText)
eventLog.Event(cfgm, v1.EventTypeWarning, nl.EventReasonInvalidValue, errorText)
return err
}
cfgParams.OIDC.AccessTimeout = accessTokensTimeout
}
if oidcRefreshTokensTimeout, exists := cfgm.Data["oidc-refresh-tokens-timeout"]; exists {
refreshTokensTimeout, err := ParseTime(oidcRefreshTokensTimeout)
if err != nil {
errorText := fmt.Sprintf("ConfigMap %s/%s: invalid value for 'oidc-refresh-tokens-timeout': %q, must be a valid nginx time (e.g. '8h', '12h', '24h')", cfgm.Namespace, cfgm.Name, oidcRefreshTokensTimeout)
nl.Warn(l, errorText)
eventLog.Event(cfgm, v1.EventTypeWarning, nl.EventReasonInvalidValue, errorText)
return err
}
cfgParams.OIDC.RefreshTimeout = refreshTokensTimeout
}
if oidcSIDSTimeout, exists := cfgm.Data["oidc-sids-timeout"]; exists {
sidsTimeout, err := ParseTime(oidcSIDSTimeout)
if err != nil {
errorText := fmt.Sprintf("ConfigMap %s/%s: invalid value for 'oidc-sids-timeout': %q, must be a valid nginx time (e.g. '8h', '12h', '24h')", cfgm.Namespace, cfgm.Name, oidcSIDSTimeout)
nl.Warn(l, errorText)
eventLog.Event(cfgm, v1.EventTypeWarning, nl.EventReasonInvalidValue, errorText)
return err
}
cfgParams.OIDC.SIDSTimeout = sidsTimeout
}
return nil
}

//nolint:gocyclo
func parseConfigMapZoneSync(l *slog.Logger, cfgm *v1.ConfigMap, cfgParams *ConfigParams, eventLog record.EventRecorder, nginxPlus bool) (*ZoneSync, error) {
if zoneSync, exists, err := GetMapKeyAsBool(cfgm.Data, "zone-sync", cfgm); exists {
Expand Down Expand Up @@ -1121,11 +1181,18 @@ func GenerateNginxMainConfig(staticCfgParams *StaticConfigParams, config *Config
InternalRouteServer: staticCfgParams.EnableInternalRoutes,
InternalRouteServerName: staticCfgParams.InternalRouteServerName,
LatencyMetrics: staticCfgParams.EnableLatencyMetrics,
OIDC: staticCfgParams.EnableOIDC,
ZoneSyncConfig: zoneSyncConfig,
DynamicSSLReloadEnabled: staticCfgParams.DynamicSSLReload,
StaticSSLPath: staticCfgParams.StaticSSLPath,
NginxVersion: staticCfgParams.NginxVersion,
OIDC: version1.OIDCConfig{
Enable: staticCfgParams.EnableOIDC,
PKCETimeout: config.OIDC.PKCETimeout,
IDTokenTimeout: config.OIDC.IDTokenTimeout,
AccessTimeout: config.OIDC.AccessTimeout,
RefreshTimeout: config.OIDC.RefreshTimeout,
SIDSTimeout: config.OIDC.SIDSTimeout,
},
ZoneSyncConfig: zoneSyncConfig,
DynamicSSLReloadEnabled: staticCfgParams.DynamicSSLReload,
StaticSSLPath: staticCfgParams.StaticSSLPath,
NginxVersion: staticCfgParams.NginxVersion,
}
return nginxCfg
}
240 changes: 240 additions & 0 deletions internal/configs/configmaps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,246 @@ func TestParseConfigMapAccessLogDefault(t *testing.T) {
}
}

func TestParseConfigMapOIDC(t *testing.T) {
t.Parallel()
tests := []struct {
configMap *v1.ConfigMap
want *OIDC
msg string
}{
{
configMap: &v1.ConfigMap{
Data: map[string]string{},
},
want: &OIDC{
PKCETimeout: "90s",
IDTokenTimeout: "1h",
AccessTimeout: "1h",
RefreshTimeout: "8h",
SIDSTimeout: "8h",
},
msg: "default OIDC values",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-pkce-timeout": "5m",
"oidc-id-tokens-timeout": "2h",
"oidc-access-tokens-timeout": "3h",
"oidc-refresh-tokens-timeout": "48h",
"oidc-sids-timeout": "72h",
},
},
want: &OIDC{
PKCETimeout: "5m",
IDTokenTimeout: "2h",
AccessTimeout: "3h",
RefreshTimeout: "48h",
SIDSTimeout: "72h",
},
msg: "all timeout values custom",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-pkce-timeout": "15m",
},
},
want: &OIDC{
PKCETimeout: "15m",
},
msg: "custom PKCE timeout only",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-id-tokens-timeout": "90m",
},
},
want: &OIDC{
IDTokenTimeout: "90m",
},
msg: "custom ID token timeout only",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-access-tokens-timeout": "4h",
},
},
want: &OIDC{
AccessTimeout: "4h",
},
msg: "custom access token timeout only",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-refresh-tokens-timeout": "16h",
},
},
want: &OIDC{
RefreshTimeout: "16h",
},
msg: "custom refresh token timeout only",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-sids-timeout": "12h",
},
},
want: &OIDC{
SIDSTimeout: "12h",
},
msg: "custom SIDS timeout only",
},
}

nginxPlus := true
hasAppProtect := false
hasAppProtectDos := false
hasTLSPassthrough := false
directiveAutoadjustEnabled := false

for _, test := range tests {
t.Run(test.msg, func(t *testing.T) {
result, configOk := ParseConfigMap(context.Background(), test.configMap, nginxPlus, hasAppProtect, hasAppProtectDos, hasTLSPassthrough, directiveAutoadjustEnabled, makeEventLogger())
if !configOk {
t.Error("want configOk true, got configOk false")
}

// Check only the specific fields that are set in the test expectation
if test.want.PKCETimeout != "" {
assert.Equal(t, test.want.PKCETimeout, result.OIDC.PKCETimeout)
}
if test.want.IDTokenTimeout != "" {
assert.Equal(t, test.want.IDTokenTimeout, result.OIDC.IDTokenTimeout)
}
if test.want.AccessTimeout != "" {
assert.Equal(t, test.want.AccessTimeout, result.OIDC.AccessTimeout)
}
if test.want.RefreshTimeout != "" {
assert.Equal(t, test.want.RefreshTimeout, result.OIDC.RefreshTimeout)
}
if test.want.SIDSTimeout != "" {
assert.Equal(t, test.want.SIDSTimeout, result.OIDC.SIDSTimeout)
}
})
}
}

func TestParseConfigMapOIDCErrors(t *testing.T) {
t.Parallel()
tests := []struct {
configMap *v1.ConfigMap
expectedErr bool
msg string
}{
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-pkce-timeout": "invalid-time",
},
},
expectedErr: true,
msg: "invalid PKCE timeout format",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-id-tokens-timeout": "abc123",
},
},
expectedErr: true,
msg: "invalid ID token timeout format",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-access-tokens-timeout": "5x",
},
},
expectedErr: true,
msg: "invalid access token timeout format",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-refresh-tokens-timeout": "",
},
},
expectedErr: true,
msg: "empty refresh token timeout",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-sids-timeout": " ",
},
},
expectedErr: true,
msg: "whitespace-only SIDS timeout",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-pkce-timeout": "-5m",
},
},
expectedErr: true,
msg: "negative PKCE timeout",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-id-tokens-timeout": "1.5h",
},
},
expectedErr: true,
msg: "decimal in ID token timeout",
},
{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-access-tokens-timeout": "5minutes",
},
},
expectedErr: true,
msg: "invalid time unit format",
},

{
configMap: &v1.ConfigMap{
Data: map[string]string{
"oidc-sids-timeout": "5s 10m",
},
},
expectedErr: true,
msg: "multiple time values without proper format",
},
}

nginxPlus := true
hasAppProtect := false
hasAppProtectDos := false
hasTLSPassthrough := false
directiveAutoadjustEnabled := false

for _, test := range tests {
t.Run(test.msg, func(t *testing.T) {
_, configOk := ParseConfigMap(context.Background(), test.configMap, nginxPlus, hasAppProtect, hasAppProtectDos, hasTLSPassthrough, directiveAutoadjustEnabled, makeEventLogger())

if test.expectedErr && configOk {
t.Errorf("want configOk false, got configOk true for %s", test.msg)
}
if !test.expectedErr && !configOk {
t.Errorf("want configOk true, got configOk false for %s", test.msg)
}
})
}
}

func TestParseMGMTConfigMapError(t *testing.T) {
t.Parallel()
tests := []struct {
Expand Down
6 changes: 0 additions & 6 deletions internal/configs/oidc/oidc_common.conf
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ map $http_x_forwarded_proto $proto {
# JWK Set will be fetched from $oidc_jwks_uri and cached here - ensure writable by nginx user
proxy_cache_path /var/cache/nginx/jwk levels=1 keys_zone=jwk:64k max_size=1m;

# Change timeout values to at least the validity period of each token type
keyval_zone zone=oidc_id_tokens:1M timeout=1h sync;
keyval_zone zone=oidc_access_tokens:1M timeout=1h sync;
keyval_zone zone=refresh_tokens:1M timeout=8h sync;
keyval_zone zone=oidc_sids:1M timeout=8h sync;

keyval $cookie_auth_token $session_jwt zone=oidc_id_tokens; # Exchange cookie for ID token(JWT)
keyval $cookie_auth_token $access_token zone=oidc_access_tokens; # Exchange cookie for access token
keyval $cookie_auth_token $refresh_token zone=refresh_tokens; # Exchange cookie for refresh token
Expand Down
Loading
Loading