Skip to content

Commit ecdcd4f

Browse files
committed
Propagate OpenShift TLS security profile to Authorino
Read the cluster's APIServer CR (apiserver.config.openshift.io/cluster) and propagate its TLS security profile to the Authorino CR on both the Listener and OIDCServer TLS configurations. On non-OpenShift clusters, falls back to Intermediate profile defaults. - Navigate the topology graph from Kuadrant CR to find the APIServer CR instead of filtering all objects; CR name defaults to "cluster" and can be overridden via APISERVER_CR_NAME env var - Map OpenShift VersionTLS12 format to 1.2 short form to match the updated Authorino CLI flag format - Add integration test script (hack/test-tls-profile.sh) covering 9 scenarios with both Authorino CR spec and deployment args verification Signed-off-by: Phil Brookes <pbrookes@redhat.com> rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED
1 parent 0bb8e0a commit ecdcd4f

23 files changed

Lines changed: 1674 additions & 204 deletions

Dockerfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ ARG WASM_SHIM_IMAGE=quay.io/kuadrant/wasm-shim:latest
33
FROM ${WASM_SHIM_IMAGE} AS wasm-shim
44

55
# Build the manager binary
6-
FROM --platform=$BUILDPLATFORM golang:1.25 AS builder
6+
FROM --platform=$BUILDPLATFORM golang:1.26 AS builder
77

88
WORKDIR /workspace
99
# Copy the Go Modules manifests
1010
COPY go.mod go.mod
1111
COPY go.sum go.sum
12+
# Copy local dependency overrides (replace directives)
13+
COPY .local-deps/ /local-deps/
14+
RUN if [ -f go.mod ] && grep -q 'replace.*\.\./authorino-operator' go.mod; then \
15+
sed -i 's|\.\./authorino-operator|/local-deps/authorino-operator|' go.mod; \
16+
fi
1217
# cache deps before building and copying source so that we don't need to re-download as much
1318
# and so that source changes don't invalidate our downloaded layer
1419
RUN go mod download

cmd/extensions/oidc-policy/api/v1alpha1/oidcpolicy_types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,23 @@ func (p *OIDCPolicy) redirectURL(igwURL *url.URL) (*url.URL, error) {
271271
return redirectURL, nil
272272
}
273273

274+
// GetBaseURL returns the base URL (scheme + host + port) for post-authentication redirects.
275+
// It derives this from spec.provider.redirectURI if set, otherwise from igwURL.
276+
func (p *OIDCPolicy) GetBaseURL(igwURL *url.URL) (*url.URL, error) {
277+
redirectURL, err := p.redirectURL(igwURL)
278+
if err != nil {
279+
return nil, err
280+
}
281+
282+
// Extract base URL (scheme + host, no path or query)
283+
baseURL := &url.URL{
284+
Scheme: redirectURL.Scheme,
285+
Host: redirectURL.Host,
286+
}
287+
288+
return baseURL, nil
289+
}
290+
274291
// +kubebuilder:object:root=true
275292

276293
// OIDCPolicyList contains a list of OIDCPolicy

cmd/extensions/oidc-policy/api/v1alpha1/oidcpolicy_types_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,103 @@ func TestOIDCPolicyStatus_Equals(t *testing.T) {
163163
}
164164
}
165165

166+
func TestGetBaseURL(t *testing.T) {
167+
tests := []struct {
168+
name string
169+
redirectURI string
170+
igwURL string
171+
expectedBase string
172+
}{
173+
{
174+
name: "No custom redirectURI - uses igwURL",
175+
redirectURI: "",
176+
igwURL: "http://gateway.example.com:8001",
177+
expectedBase: "http://gateway.example.com:8001",
178+
},
179+
{
180+
name: "Custom redirectURI with non-standard port",
181+
redirectURI: "https://public.example.com:8443/auth/callback",
182+
igwURL: "http://gateway.example.com:8001",
183+
expectedBase: "https://public.example.com:8443",
184+
},
185+
{
186+
name: "Custom redirectURI with standard port",
187+
redirectURI: "https://public.example.com/auth/callback",
188+
igwURL: "http://gateway.example.com:8001",
189+
expectedBase: "https://public.example.com",
190+
},
191+
{
192+
name: "Custom redirectURI with different scheme",
193+
redirectURI: "http://external.example.com:9000/custom/callback",
194+
igwURL: "https://gateway.example.com",
195+
expectedBase: "http://external.example.com:9000",
196+
},
197+
}
198+
199+
for _, tt := range tests {
200+
t.Run(tt.name, func(t *testing.T) {
201+
policy := &OIDCPolicy{
202+
Spec: OIDCPolicySpec{
203+
OIDCPolicySpecProper: OIDCPolicySpecProper{
204+
Provider: &Provider{
205+
IssuerURL: "https://issuer.com",
206+
ClientID: "client123",
207+
RedirectURI: tt.redirectURI,
208+
},
209+
},
210+
},
211+
}
212+
213+
igwURL, err := url.Parse(tt.igwURL)
214+
if err != nil {
215+
t.Fatal(err)
216+
}
217+
218+
baseURL, err := policy.GetBaseURL(igwURL)
219+
if err != nil {
220+
t.Fatalf("GetBaseURL() error = %v", err)
221+
}
222+
223+
if baseURL.String() != tt.expectedBase {
224+
t.Errorf("GetBaseURL() = %v, want %v", baseURL.String(), tt.expectedBase)
225+
}
226+
})
227+
}
228+
}
229+
230+
func TestGetBaseURL_ExtractsBaseFromRedirectURI(t *testing.T) {
231+
policy := &OIDCPolicy{
232+
Spec: OIDCPolicySpec{
233+
OIDCPolicySpecProper: OIDCPolicySpecProper{
234+
Provider: &Provider{
235+
IssuerURL: "https://issuer.com",
236+
ClientID: "client123",
237+
RedirectURI: "https://public.example.com:8443/auth/callback?foo=bar",
238+
},
239+
},
240+
},
241+
}
242+
243+
igwURL, _ := url.Parse("http://gateway.example.com:8001")
244+
baseURL, err := policy.GetBaseURL(igwURL)
245+
if err != nil {
246+
t.Fatalf("GetBaseURL() error = %v", err)
247+
}
248+
249+
// Base URL should only have scheme and host, no path or query
250+
if baseURL.String() != "https://public.example.com:8443" {
251+
t.Errorf("GetBaseURL() = %v, want https://public.example.com:8443", baseURL.String())
252+
}
253+
254+
if baseURL.Path != "" {
255+
t.Errorf("GetBaseURL() path should be empty, got %v", baseURL.Path)
256+
}
257+
258+
if baseURL.RawQuery != "" {
259+
t.Errorf("GetBaseURL() query should be empty, got %v", baseURL.RawQuery)
260+
}
261+
}
262+
166263
func mockMinimalOIDCPolicy() *OIDCPolicy {
167264
return &OIDCPolicy{
168265
TypeMeta: metav1.TypeMeta{},

cmd/extensions/oidc-policy/internal/controller/oidcpolicy_reconciler.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,21 @@ type ingressGatewayInfo struct {
4040
Name string `json:"name"`
4141
Namespace string `json:"namespace"`
4242
Protocol gatewayapiv1.ProtocolType `json:"protocol"`
43+
Port int32 `json:"port"`
4344
url *url.URL
4445
}
4546

4647
func (g *ingressGatewayInfo) GetURL() *url.URL {
4748
if g.url == nil {
49+
host := g.Hostname
50+
// Include port if it's not the standard port for the protocol
51+
if (g.Protocol == gatewayapiv1.HTTPProtocolType && g.Port != 80) ||
52+
(g.Protocol == gatewayapiv1.HTTPSProtocolType && g.Port != 443) {
53+
host = fmt.Sprintf("%s:%d", g.Hostname, g.Port)
54+
}
4855
g.url = &url.URL{
4956
Scheme: strings.ToLower(string(g.Protocol)),
50-
Host: g.Hostname,
57+
Host: host,
5158
}
5259
}
5360
return g.url
@@ -109,6 +116,7 @@ func (r *OIDCPolicyReconciler) Reconcile(ctx context.Context, request reconcile.
109116
oidcPolicy,
110117
`{"protocol": self.findGateways()[0].spec.listeners[0].protocol,
111118
"hostname": self.findGateways()[0].spec.listeners[0].hostname,
119+
"port": self.findGateways()[0].spec.listeners[0].port,
112120
"name": self.findGateways()[0].metadata.name,
113121
"namespace": self.findGateways()[0].metadata.namespace}`,
114122
true)
@@ -297,6 +305,11 @@ func claimPredicate(k, v string) string {
297305
return fmt.Sprintf(`has(auth.identity) && has(auth.identity.%s) && (auth.identity.%s == "%s" || (type(auth.identity.%s) == list && "%s" in auth.identity.%s))`, k, k, v, k, v, k)
298306
}
299307

308+
func buildTargetCookieExpression(hostname string, protocol gatewayapiv1.ProtocolType) string {
309+
return fmt.Sprintf(`
310+
"target=" + request.url_path + (has(request.query) && request.query != "" ? "?" + request.query : "") + "; domain=%s; HttpOnly; %s SameSite=Lax; Path=/; Max-Age=3600"`, hostname, getSecureFlag(protocol))
311+
}
312+
300313
func buildMainAuthPolicy(pol *v1alpha1.OIDCPolicy, igw *ingressGatewayInfo) (*kuadrantv1.AuthPolicy, error) {
301314
authorizeURL, err := pol.GetAuthorizeURL(igw.GetURL())
302315
if err != nil {
@@ -307,8 +320,7 @@ func buildMainAuthPolicy(pol *v1alpha1.OIDCPolicy, igw *ingressGatewayInfo) (*ku
307320
return nil, err
308321
}
309322

310-
setCookie := fmt.Sprintf(`
311-
"target=" + request.path + "; domain=%s; HttpOnly; %s SameSite=Lax; Path=/; Max-Age=3600"`, igw.Hostname, getSecureFlag(igw.Protocol))
323+
setCookie := buildTargetCookieExpression(igw.Hostname, igw.Protocol)
312324

313325
var authorization = map[string]kuadrantv1.MergeableAuthorizationSpec{}
314326
var authPatterns []authorinov1beta3.PatternExpressionOrRef
@@ -442,6 +454,14 @@ func buildCallbackHTTPRoute(pol *v1alpha1.OIDCPolicy, igw *ingressGatewayInfo) *
442454
}
443455
}
444456

457+
func buildOpaAuthorizationRule(baseURL *url.URL, igwURL *url.URL, authorizeURL string) string {
458+
return fmt.Sprintf(`cookies := { name: value | raw_cookies := input.request.headers.cookie; cookie_parts := split(raw_cookies, ";"); part := cookie_parts[_]; trimmed := trim(part, " "); eq_idx := indexof(trimmed, "="); eq_idx != -1; name := trim(substring(trimmed, 0, eq_idx), " "); value := trim(substring(trimmed, eq_idx + 1, -1), " ")}
459+
location := concat("", ["%s", cookies.target]) { input.auth.metadata.token.id_token; cookies.target }
460+
location := "%s" { input.auth.metadata.token.id_token; not cookies.target }
461+
location := "%s" { not input.auth.metadata.token.id_token }
462+
allow = true`, baseURL, igwURL, authorizeURL)
463+
}
464+
445465
func buildCallbackAuthPolicy(pol *v1alpha1.OIDCPolicy, igw *ingressGatewayInfo) (*kuadrantv1.AuthPolicy, error) {
446466
igwURL := igw.GetURL()
447467
tokenRequestURL, err := pol.GetTokenRequestURL()
@@ -458,6 +478,12 @@ func buildCallbackAuthPolicy(pol *v1alpha1.OIDCPolicy, igw *ingressGatewayInfo)
458478
return nil, err
459479
}
460480

481+
// Get the base URL for post-auth redirects (respects custom redirectURI if set)
482+
baseURL, err := pol.GetBaseURL(igwURL)
483+
if err != nil {
484+
return nil, err
485+
}
486+
461487
callbackRoute := gatewayapiv1alpha2.LocalPolicyTargetReference{
462488
Group: gatewayapiv1alpha2.GroupName,
463489
Kind: gatewayapiv1alpha2.Kind("HTTPRoute"),
@@ -470,11 +496,7 @@ func buildCallbackAuthPolicy(pol *v1alpha1.OIDCPolicy, igw *ingressGatewayInfo)
470496
Expression: authorinov1beta3.CelExpression(callBodyCelExpression),
471497
}
472498

473-
opaAuthorizationRule := fmt.Sprintf(`cookies := { name: value | raw_cookies := input.request.headers.cookie; cookie_parts := split(raw_cookies, ";"); part := cookie_parts[_]; kv := split(trim(part, " "), "="); count(kv) == 2; name := trim(kv[0], " "); value := trim(kv[1], " ")}
474-
location := concat("", ["%s", cookies.target]) { input.auth.metadata.token.id_token; cookies.target }
475-
location := "%s" { input.auth.metadata.token.id_token; not cookies.target }
476-
location := "%s" { not input.auth.metadata.token.id_token }
477-
allow = true`, igwURL, igwURL, authorizeURL)
499+
opaAuthorizationRule := buildOpaAuthorizationRule(baseURL, igwURL, authorizeURL)
478500

479501
return &kuadrantv1.AuthPolicy{
480502
TypeMeta: metav1.TypeMeta{

0 commit comments

Comments
 (0)