Skip to content
Open
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
45 changes: 45 additions & 0 deletions api/v1beta3/auth_config_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,15 @@ type JwtAuthenticationSpec struct {
// +optional
// +kubebuilder:validation:Minimum:=0
Timeout *int `json:"timeout,omitempty"`

// Maximum number of bytes to read from OIDC discovery and JWKS HTTP response bodies.
// When set, response body readers will be limited to this size, preventing
// unbounded memory consumption from unexpectedly large responses.
// If the response exceeds this limit, the truncation will cause a decode error.
// If omitted, no limit is applied.
// +optional
// +kubebuilder:validation:Minimum:=1
MaxResponseBytes *int64 `json:"maxResponseBytes,omitempty"`
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Settings to perform the OAuth2 token introspection request.
Expand All @@ -420,6 +429,15 @@ type OAuth2TokenIntrospectionSpec struct {
// +optional
// +kubebuilder:validation:Minimum:=0
Timeout *int `json:"timeout,omitempty"`

// Maximum number of bytes to read from the token introspection HTTP response body.
// When set, the response body reader will be limited to this size, preventing
// unbounded memory consumption from unexpectedly large responses.
// If the response is JSON and exceeds this limit, the truncation will cause a decode error.
// If omitted, no limit is applied.
// +optional
// +kubebuilder:validation:Minimum:=1
MaxResponseBytes *int64 `json:"maxResponseBytes,omitempty"`
}

// Parameters of the Kubernetes TokenReview request
Expand Down Expand Up @@ -600,6 +618,15 @@ type HttpEndpointSpec struct {
// +optional
// +kubebuilder:validation:Minimum:=0
Timeout *int `json:"timeout,omitempty"`

// Maximum number of bytes to read from the HTTP response body.
// When set, the response body reader will be limited to this size, preventing
// unbounded memory consumption from unexpectedly large responses.
// If the response is JSON and exceeds this limit, the truncation will cause a decode error.
// If omitted, no limit is applied.
// +optional
// +kubebuilder:validation:Minimum:=1
MaxResponseBytes *int64 `json:"maxResponseBytes,omitempty"`
}

// +kubebuilder:validation:Enum:=GET;POST;PUT;PATCH;DELETE;HEAD;OPTIONS;CONNECT;TRACE
Expand Down Expand Up @@ -666,6 +693,15 @@ type UserInfoMetadataSpec struct {
// +optional
// +kubebuilder:validation:Minimum:=0
Timeout *int `json:"timeout,omitempty"`

// Maximum number of bytes to read from the UserInfo HTTP response body.
// When set, the response body reader will be limited to this size, preventing
// unbounded memory consumption from unexpectedly large responses.
// If the response exceeds this limit, the truncation will cause a decode error.
// If omitted, no limit is applied.
// +optional
// +kubebuilder:validation:Minimum:=1
MaxResponseBytes *int64 `json:"maxResponseBytes,omitempty"`
}

// Settings of the User-Managed Access (UMA) source of resource data.
Expand All @@ -686,6 +722,15 @@ type UmaMetadataSpec struct {
// +optional
// +kubebuilder:validation:Minimum:=0
Timeout *int `json:"timeout,omitempty"`

// Maximum number of bytes to read from UMA HTTP response bodies (discovery, PAT, resource queries).
// When set, response body readers will be limited to this size, preventing
// unbounded memory consumption from unexpectedly large responses.
// If the response exceeds this limit, the truncation will cause a decode error.
// If omitted, no limit is applied.
// +optional
// +kubebuilder:validation:Minimum:=1
MaxResponseBytes *int64 `json:"maxResponseBytes,omitempty"`
}

// +kubebuilder:validation:XValidation:rule="has(self.patternMatching) ? !(has(self.opa) || has(self.kubernetesSubjectAccessReview) || has(self.spicedb)) : has(self.opa) ? !(has(self.kubernetesSubjectAccessReview) || has(self.spicedb)) : has(self.kubernetesSubjectAccessReview) != has(self.spicedb)",message="Use exactly one of: patternMatching, opa, kubernetesSubjectAccessReview, spicedb"
Expand Down
25 changes: 25 additions & 0 deletions api/v1beta3/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 35 additions & 8 deletions controllers/auth_config_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,15 +375,22 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf
authCred,
)
oauth2.Timeout = oauth2Identity.Timeout
if oauth2Identity.MaxResponseBytes != nil {
oauth2.MaxResponseBytes = *oauth2Identity.MaxResponseBytes
}
translatedIdentity.OAuth2 = oauth2

// oidc
case api.JwtAuthentication:
var jwtVerifier identity_evaluators.JWTVerifier
var jwtMaxResponseBytes int64
if identity.Jwt.MaxResponseBytes != nil {
jwtMaxResponseBytes = *identity.Jwt.MaxResponseBytes
}
if identity.Jwt.IssuerUrl != "" {
jwtVerifier = identity_evaluators.NewOIDCProviderVerifier(ctx, identity.Jwt.IssuerUrl, identity.Jwt.TTL, identity.Jwt.Timeout)
jwtVerifier = identity_evaluators.NewOIDCProviderVerifier(ctx, identity.Jwt.IssuerUrl, identity.Jwt.TTL, identity.Jwt.Timeout, jwtMaxResponseBytes)
} else if identity.Jwt.JwksUrl != "" {
jwtVerifier = identity_evaluators.NewJwksVerifier(ctx, identity.Jwt.JwksUrl, identity.Jwt.Timeout)
jwtVerifier = identity_evaluators.NewJwksVerifier(ctx, identity.Jwt.JwksUrl, identity.Jwt.Timeout, jwtMaxResponseBytes)
} else {
return nil, fmt.Errorf("missing issuerUrl or jwksUrl for JWT authentication method") // should never happen if properly validated at the API level
}
Expand Down Expand Up @@ -499,17 +506,22 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf
return nil, err // TODO: Review this error, perhaps we don't need to return an error, just reenqueue.
}

var umaMaxResponseBytes int64
if metadata.Uma.MaxResponseBytes != nil {
umaMaxResponseBytes = *metadata.Uma.MaxResponseBytes
}
if uma, err := metadata_evaluators.NewUMAMetadata(
ctx,
metadata.Uma.Endpoint,
string(secret.Data["clientID"]),
string(secret.Data["clientSecret"]),
metadata.Uma.Timeout,
umaMaxResponseBytes,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to create UMA metadata evaluator")
return nil, err
} else {
uma.Timeout = metadata.Uma.Timeout
translatedMetadata.UMA = uma
}

Expand All @@ -527,6 +539,9 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf
}
userInfo := metadata_evaluators.NewUserInfo(openIdConfigStore, metadata.UserInfo.UserInfoUrl)
userInfo.Timeout = metadata.UserInfo.Timeout
if metadata.UserInfo.MaxResponseBytes != nil {
userInfo.MaxResponseBytes = *metadata.UserInfo.MaxResponseBytes
}
translatedMetadata.UserInfo = userInfo

// generic http
Expand Down Expand Up @@ -609,12 +624,18 @@ func (r *AuthConfigReconciler) translateAuthConfig(ctx context.Context, authConf
sharedSecret = string(secret.Data[externalRegistry.SharedSecret.Key])
}

var opaMaxResponseBytes int64
if externalRegistry.MaxResponseBytes != nil {
opaMaxResponseBytes = *externalRegistry.MaxResponseBytes
}

externalSource = &authorization_evaluators.OPAExternalSource{
Endpoint: externalRegistry.Url,
SharedSecret: sharedSecret,
AuthCredentials: newAuthCredential(externalRegistry.Credentials),
TTL: externalRegistry.TTL,
Timeout: externalRegistry.Timeout,
Endpoint: externalRegistry.Url,
SharedSecret: sharedSecret,
AuthCredentials: newAuthCredential(externalRegistry.Credentials),
TTL: externalRegistry.TTL,
Timeout: externalRegistry.Timeout,
MaxResponseBytes: opaMaxResponseBytes,
}
}

Expand Down Expand Up @@ -1221,6 +1242,11 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht
}
}

var maxResponseBytes int64
if http.MaxResponseBytes != nil {
maxResponseBytes = *http.MaxResponseBytes
}

ev := &metadata_evaluators.GenericHttp{
Endpoint: http.Url,
DynamicEndpoint: dynamicEndpoint,
Expand All @@ -1233,6 +1259,7 @@ func (r *AuthConfigReconciler) buildGenericHttpEvaluator(ctx context.Context, ht
OAuth2: oauth2ClientCredentialsConfig,
OAuth2TokenForceFetch: oauth2TokenForceFetch,
Timeout: http.Timeout,
MaxResponseBytes: maxResponseBytes,
}

if sharedSecret != "" || oauth2ClientCredentialsConfig != nil {
Expand Down
29 changes: 29 additions & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,35 @@ In both cases, the location where the secret (long-lived or OAuth2 access token)

Custom headers can be set with the `headers` field. Nevertheless, headers such as `Content-Type` and `Authorization` (or eventual custom header used for carrying the authentication secret, set instead via the `credentials` option) will be superseded by the respective values defined for the fields `contentType` and `sharedSecretRef`.

#### Limiting response size (`maxResponseBytes`)

To protect Authorino from memory exhaustion caused by unexpectedly large HTTP responses from external services, set the `maxResponseBytes` field to cap the number of bytes read from the response body.

```yaml
spec:
metadata:
my-metadata:
http:
url: https://external-service/metadata
maxResponseBytes: 65536 # 64 KiB
```

The same field is available in:
- `spec.callbacks.*.http` — HTTP callbacks
- `spec.authorization.*.opa.externalPolicy` — external OPA policy bundles
- `spec.authentication.*.jwt` — OIDC discovery and JWKS fetching
- `spec.authentication.*.oauth2Introspection` — OAuth2 token introspection
- `spec.metadata.*.userInfo` — OIDC UserInfo
- `spec.metadata.*.uma` — UMA resource registry (applies to discovery, PAT requests, and resource queries)

> **Important:** When a response exceeds the limit, the body is truncated to the specified size. For `application/json` responses, this truncation produces malformed JSON that can cause a decode error. As a result, the evaluator will fail and the corresponding piece of the auth pipeline will not be available.
>
> Depending on how downstream authorization policies handle missing metadata, the outcome can vary:
> - **Policies that deny access when metadata is absent**: the request will be denied. This is the safer default.
> - **Policies that fall back to granting access on the absence of metadata**: truncation may cause the policy to inadvertently allow a request that should have been denied. Review your authorization policies to ensure they do not assume missing metadata means "allowed."
>
> When `maxResponseBytes` is omitted, no limit is applied and the full response body is read.

### OIDC UserInfo ([`metadata.userInfo`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta3?utm_source=gopls#UserInfoMetadataSpec))

Online fetching of OpenID Connect (OIDC) UserInfo data (phase ii of the Authorino [Auth Pipeline](./architecture.md#the-auth-pipeline-aka-enforcing-protection-in-request-time)), associated with an OIDC identity source configured and resolved in phase (i).
Expand Down
70 changes: 70 additions & 0 deletions install/crd/authorino.kuadrant.io_authconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,16 @@ spec:
The JSON Web Keys (JWK) obtained from this endpoint are automatically cached and the caching updated whenever the kid of a JWT does not match any of the cached JWKs (https://openid.net/specs/openid-connect-core-1_0.html#RotateSigKeys).
One of: jwksUrl, issuerUrl
type: string
maxResponseBytes:
description: |-
Maximum number of bytes to read from OIDC discovery and JWKS HTTP response bodies.
When set, response body readers will be limited to this size, preventing
unbounded memory consumption from unexpectedly large responses.
If the response exceeds this limit, the truncation will cause a decode error.
If omitted, no limit is applied.
format: int64
minimum: 1
type: integer
timeout:
description: |-
Timeout for OIDC discovery and JWK fetching HTTP requests, in milliseconds.
Expand Down Expand Up @@ -319,6 +329,16 @@ spec:
IMPORTANT: Ensure this URL points to a trusted OAuth2 server. If this value can be influenced by user input,
you may be vulnerable to Server-Side Request Forgery (SSRF) attacks.
type: string
maxResponseBytes:
description: |-
Maximum number of bytes to read from the token introspection HTTP response body.
When set, the response body reader will be limited to this size, preventing
unbounded memory consumption from unexpectedly large responses.
If the response is JSON and exceeds this limit, the truncation will cause a decode error.
If omitted, no limit is applied.
format: int64
minimum: 1
type: integer
timeout:
description: |-
Timeout for the token introspection HTTP request, in milliseconds.
Expand Down Expand Up @@ -907,6 +927,16 @@ spec:
type: object
description: Custom headers in the HTTP request.
type: object
maxResponseBytes:
description: |-
Maximum number of bytes to read from the HTTP response body.
When set, the response body reader will be limited to this size, preventing
unbounded memory consumption from unexpectedly large responses.
If the response is JSON and exceeds this limit, the truncation will cause a decode error.
If omitted, no limit is applied.
format: int64
minimum: 1
type: integer
method:
default: GET
description: |-
Expand Down Expand Up @@ -1472,6 +1502,16 @@ spec:
type: object
description: Custom headers in the HTTP request.
type: object
maxResponseBytes:
description: |-
Maximum number of bytes to read from the HTTP response body.
When set, the response body reader will be limited to this size, preventing
unbounded memory consumption from unexpectedly large responses.
If the response is JSON and exceeds this limit, the truncation will cause a decode error.
If omitted, no limit is applied.
format: int64
minimum: 1
type: integer
method:
default: GET
description: |-
Expand Down Expand Up @@ -1836,6 +1876,16 @@ spec:
type: object
description: Custom headers in the HTTP request.
type: object
maxResponseBytes:
description: |-
Maximum number of bytes to read from the HTTP response body.
When set, the response body reader will be limited to this size, preventing
unbounded memory consumption from unexpectedly large responses.
If the response is JSON and exceeds this limit, the truncation will cause a decode error.
If omitted, no limit is applied.
format: int64
minimum: 1
type: integer
method:
default: GET
description: |-
Expand Down Expand Up @@ -2001,6 +2051,16 @@ spec:
IMPORTANT: Ensure this URL points to a trusted UMA server. If this value can be influenced by user input,
you may be vulnerable to Server-Side Request Forgery (SSRF) attacks.
type: string
maxResponseBytes:
description: |-
Maximum number of bytes to read from UMA HTTP response bodies (discovery, PAT, resource queries).
When set, response body readers will be limited to this size, preventing
unbounded memory consumption from unexpectedly large responses.
If the response exceeds this limit, the truncation will cause a decode error.
If omitted, no limit is applied.
format: int64
minimum: 1
type: integer
timeout:
description: |-
Timeout for UMA HTTP requests (discovery, PAT, resource queries), in milliseconds.
Expand All @@ -2021,6 +2081,16 @@ spec:
Name of an OIDC JWT authentication rule whose obtained configuration includes an "userinfo_endpoint" claim.
One of: identitySource, userInfoUrl
type: string
maxResponseBytes:
description: |-
Maximum number of bytes to read from the UserInfo HTTP response body.
When set, the response body reader will be limited to this size, preventing
unbounded memory consumption from unexpectedly large responses.
If the response exceeds this limit, the truncation will cause a decode error.
If omitted, no limit is applied.
format: int64
minimum: 1
type: integer
timeout:
description: |-
Timeout for the UserInfo HTTP request, in milliseconds.
Expand Down
Loading
Loading