This is an automated email from the ASF dual-hosted git repository. tdiesler pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new edb78e8fafd CAMEL-21899: camel-oauth - openshift does not maintain http session edb78e8fafd is described below commit edb78e8fafd67d62c5877cab1053c67cd40da92c Author: Thomas Diesler <tdies...@redhat.com> AuthorDate: Tue Apr 15 12:42:31 2025 +0200 CAMEL-21899: camel-oauth - openshift does not maintain http session --- components/camel-oauth/helm/README.md | 21 ++++++++++++ components/camel-oauth/helm/etc/camel-realm.json | 5 +-- .../apache/camel/oauth/AbstractOAuthProcessor.java | 9 ----- .../apache/camel/oauth/InMemorySessionStore.java | 14 ++++++++ .../apache/camel/oauth/OAuthCodeFlowCallback.java | 3 +- .../apache/camel/oauth/OAuthCodeFlowProcessor.java | 32 +++++++++++++++--- .../apache/camel/oauth/OAuthLogoutProcessor.java | 7 ++-- .../apache/camel/test/oauth/SSLCertTrustTest.java | 38 +++++++++++++++++++++- .../modules/ROOT/pages/camel-jbang-kubernetes.adoc | 7 +--- .../commands/kubernetes/traits/IngressTrait.java | 9 +++-- 10 files changed, 115 insertions(+), 30 deletions(-) diff --git a/components/camel-oauth/helm/README.md b/components/camel-oauth/helm/README.md index 6563035d3c2..c7f7bbedad5 100644 --- a/components/camel-oauth/helm/README.md +++ b/components/camel-oauth/helm/README.md @@ -214,3 +214,24 @@ Verify access to the OIDC configuration ``` curl -s https://keycloak.${OPENSHIFT_HOSTNAME}/realms/camel/.well-known/openid-configuration | jq . ``` + +### Modify Keycloak OIDC Config for OpenShift + +```sh +kcadm config credentials --server https://keycloak.${OPENSHIFT_HOSTNAME} --realm master --user admin --password admin + +# Show client config +kcadm get clients -r camel | jq '.[] | select(.clientId=="camel-client")' + +CLIENT_ID=$(kcadm get clients -r camel --fields id,clientId | jq -r '.[] | select(.clientId=="camel-client").id') + +# Update redirect URIs +kcadm update clients/$CLIENT_ID -r camel -s 'redirectUris=["https://webapp.'${OPENSHIFT_HOSTNAME}'/auth"]' + +# Update post-logout redirect URIs +kcadm update clients/$CLIENT_ID -r camel -s 'attributes."post.logout.redirect.uris"="https://webapp.'${OPENSHIFT_HOSTNAME}'/"' + +kcadm get realms | jq '.[] | select(.realm=="camel")' + +kcadm get keys -r camel +``` \ No newline at end of file diff --git a/components/camel-oauth/helm/etc/camel-realm.json b/components/camel-oauth/helm/etc/camel-realm.json index ef300a59d29..281d6960253 100644 --- a/components/camel-oauth/helm/etc/camel-realm.json +++ b/components/camel-oauth/helm/etc/camel-realm.json @@ -168,10 +168,11 @@ "consentRequired" : false, "fullScopeAllowed" : false, "redirectUris": [ - "http://127.0.0.1:8080/auth" + "https://example.local/auth", + "https://example.k3s/auth" ], "attributes": { - "post.logout.redirect.uris": "http://127.0.0.1:8080/" + "post.logout.redirect.uris": "https://example.local/##https://example.k3s/" } }, { diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java index 25614aee067..a5dac7ad693 100644 --- a/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java +++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/AbstractOAuthProcessor.java @@ -26,8 +26,6 @@ import org.apache.camel.Processor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.camel.oauth.OAuth.CAMEL_OAUTH_COOKIE; - public abstract class AbstractOAuthProcessor implements Processor { protected final Logger log = LoggerFactory.getLogger(getClass()); @@ -71,11 +69,4 @@ public abstract class AbstractOAuthProcessor implements Processor { msg.setHeader("Location", redirectUrl); msg.setBody(""); } - - protected void setSessionCookie(Message msg, OAuthSession session) { - var sessionId = session.getSessionId(); - var cookie = "%s=%s; Path=/; HttpOnly; SameSite=None; Secure".formatted(CAMEL_OAUTH_COOKIE, sessionId); - msg.setHeader("Set-Cookie", cookie); - log.debug("Set-Cookie: {}", cookie); - } } diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java index 04ad43bc01a..22244eae6c9 100644 --- a/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java +++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/InMemorySessionStore.java @@ -24,6 +24,7 @@ import java.util.UUID; import java.util.stream.Collectors; import org.apache.camel.Exchange; +import org.apache.camel.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,6 +70,8 @@ public class InMemorySessionStore implements OAuthSessionStore { var msg = exchange.getMessage(); log.info("New OAuthSession: {}", sessionId); + + setSessionCookie(msg, session); msg.setHeader(OAuth.CAMEL_OAUTH_SESSION_ID, sessionId); return session; @@ -93,4 +96,15 @@ public class InMemorySessionStore implements OAuthSessionStore { return cookieMap; } + + private void setSessionCookie(Message msg, OAuthSession session) { + var sessionId = session.getSessionId(); + var cookieId = "%s=%s".formatted(CAMEL_OAUTH_COOKIE, sessionId); + if (msg.getHeader("Set-Cookie") != null) { + throw new IllegalStateException("Duplicate 'Set-Cookie' header"); + } + var cookie = cookieId + "; Path=/; HttpOnly; SameSite=None; Secure"; + msg.setHeader("Set-Cookie", cookie); + log.debug("Set-Cookie: {}", cookie); + } } diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallback.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallback.java index d18c429d492..5ad86652f14 100644 --- a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallback.java +++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowCallback.java @@ -66,9 +66,8 @@ public class OAuthCodeFlowCallback extends AbstractOAuthProcessor { postLoginUrl = postLoginUrl.substring(0, lastSlashIdx + 1); } - setSessionCookie(msg, session); sendRedirect(msg, postLoginUrl); - log.info("{} - Done", procName); + log.info("{} - Redirect to {}", procName, postLoginUrl); } } diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java index 6a9cbf13f9e..537ca8188b2 100644 --- a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java +++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthCodeFlowProcessor.java @@ -17,6 +17,7 @@ package org.apache.camel.oauth; import org.apache.camel.Exchange; +import org.apache.camel.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,23 +50,46 @@ public class OAuthCodeFlowProcessor extends AbstractOAuthProcessor { // if (session.getUserProfile().isPresent()) { authenticateExistingUserProfile(oauth, session); + log.info("{} - Done", procName); } // Fallback to the authorization code flow // if (session.getUserProfile().isEmpty()) { - var postLoginUrl = msg.getHeader(Exchange.HTTP_URL, String.class); - session.putValue("OAuthPostLoginUrl", postLoginUrl); + session.putValue("OAuthPostLoginUrl", getPostLoginUrl(msg)); var redirectUri = getRequiredProperty(exchange.getContext(), CAMEL_OAUTH_REDIRECT_URI); var params = new OAuthCodeFlowParams().setRedirectUri(redirectUri); var authRequestUrl = oauth.buildCodeFlowAuthRequestUrl(params); - setSessionCookie(msg, session); sendRedirect(msg, authRequestUrl); + log.info("{} - Redirect to {}", procName, authRequestUrl); } + } - log.info("{} - Done", procName); + private String getPostLoginUrl(Message msg) { + String postLoginUrl; + var xProto = msg.getHeader("X-Forwarded-Proto", String.class); + var xHost = msg.getHeader("X-Forwarded-Host", String.class); + var xPort = msg.getHeader("X-Forwarded-Port", Integer.class); + if (xProto != null && xHost != null) { + postLoginUrl = xProto + "://" + xHost; + if (xPort != null) { + if (xProto.equals("https") && xPort != 443) { + postLoginUrl += ":" + xPort; + } + if (xProto.equals("http") && xPort != 80) { + postLoginUrl += ":" + xPort; + } + } + var httpPath = msg.getHeader(Exchange.HTTP_PATH, String.class); + if (httpPath != null && !httpPath.isEmpty()) { + postLoginUrl += httpPath; + } + } else { + postLoginUrl = msg.getHeader(Exchange.HTTP_URL, String.class); + } + return postLoginUrl; } } diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutProcessor.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutProcessor.java index d0aea8626dd..435086dc1e1 100644 --- a/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutProcessor.java +++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/OAuthLogoutProcessor.java @@ -26,6 +26,7 @@ public class OAuthLogoutProcessor extends AbstractOAuthProcessor { @Override public void process(Exchange exchange) { var context = exchange.getContext(); + var msg = exchange.getMessage(); findOAuth(context).ifPresent(oauth -> { @@ -41,10 +42,8 @@ public class OAuthLogoutProcessor extends AbstractOAuthProcessor { var logoutUrl = oauth.buildLogoutRequestUrl(params); - log.info("OAuth logout: {}", logoutUrl); - exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 302); - exchange.getMessage().setHeader("Location", logoutUrl); - exchange.getMessage().setBody(""); + log.info("{} - Logout, then {}", procName, postLogoutUrl); + sendRedirect(msg, logoutUrl); }); }); } diff --git a/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/SSLCertTrustTest.java b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/SSLCertTrustTest.java index 31fbfccdbe5..1f94f1cbab2 100644 --- a/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/SSLCertTrustTest.java +++ b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/SSLCertTrustTest.java @@ -16,11 +16,17 @@ */ package org.apache.camel.test.oauth; +import java.io.FileInputStream; import java.io.IOException; import java.net.URL; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.TrustManagerFactory; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; @@ -29,7 +35,37 @@ import org.junit.jupiter.api.Test; class SSLCertTrustTest extends AbstractKeycloakTest { @Test - void testTrustedCertificate() { + void testCheckClusterCertificateTrust() throws Exception { + + // Load certificate to check + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + FileInputStream fis = new FileInputStream("helm/etc/cluster.crt"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(fis); + + // Load default Java truststore + FileInputStream trustStream = new FileInputStream(System.getProperty("java.home") + "/lib/security/cacerts"); + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(trustStream, "changeit".toCharArray()); + + // Initialize TrustManager + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + + try { + for (var tm : tmf.getTrustManagers()) { + var xtm = (javax.net.ssl.X509TrustManager) tm; + xtm.checkServerTrusted(new X509Certificate[] { cert }, "RSA"); + } + } catch (CertificateException ex) { + System.err.println("Untrusted, because of: " + ex); + return; + } + + System.out.println("Trusted"); + } + + @Test + void testCheckKeycloakCertificateTrust() { var admin = new KeycloakAdmin(new KeycloakAdmin.AdminParams(KEYCLOAK_BASE_URL)); Assumptions.assumeTrue(admin.isKeycloakRunning(), "Keycloak is not running"); Assertions.assertDoesNotThrow(() -> connectToUrl(KEYCLOAK_BASE_URL), "Certificate should be trusted"); diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc index cac96c85cec..58aec45a7f7 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc @@ -103,9 +103,6 @@ The Camel JBang Kubernetes export command provides several options to customize |======================================================================= |Option |Description -|--trait-profile -|The trait profile to use for the deployment. - |--service-account |The service account used to run the application. @@ -1135,8 +1132,6 @@ spec: The Route trait enhances the Kubernetes manifest with a Route resource to expose the application to the outside world. This requires the presence in the Kubernetes manifest of a Service Resource. -NOTE: You need to enable the OpenShift profile trait with `--trait-profile=openshift` option. - The Route trait provides the following configuration options: [cols="2m,1m,5a"] @@ -1193,7 +1188,7 @@ You may specify these options with the export command to customize the Route Res [source,bash] ---- -camel kubernetes export Sample.java --trait-profile=openshift -t route.enabled=true --trait route.host=example.com -t route.tls-termination=edge -t route.tls-certificate=file:/tmp/tls.crt -t route.tls-key=file:/tmp/tls.key +camel kubernetes export Sample.java --cluster-type=openshift -t route.enabled=true --trait route.host=example.com -t route.tls-termination=edge -t route.tls-certificate=file:/tmp/tls.crt -t route.tls-key=file:/tmp/tls.key ---- diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/IngressTrait.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/IngressTrait.java index e1eb76f4f20..62881712e38 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/IngressTrait.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/IngressTrait.java @@ -16,6 +16,7 @@ */ package org.apache.camel.dsl.jbang.core.commands.kubernetes.traits; +import java.util.List; import java.util.Optional; import io.fabric8.kubernetes.api.model.networking.v1.HTTPIngressPath; @@ -86,8 +87,9 @@ public class IngressTrait extends BaseTrait { .endBackend() .build(); + var ingressHost = Optional.ofNullable(ingressTrait.getHost()).orElse(DEFAULT_INGRESS_HOST); IngressRule rule = new IngressRuleBuilder() - .withHost(Optional.ofNullable(ingressTrait.getHost()).orElse(DEFAULT_INGRESS_HOST)) + .withHost(ingressHost) .withNewHttp() .withPaths(path) .endHttp() @@ -99,7 +101,10 @@ public class IngressTrait extends BaseTrait { .withRules(rule) .endSpec(); - if (ingressTrait.getTlsHosts() != null && ingressTrait.getTlsSecretName() != null) { + if (ingressTrait.getTlsSecretName() != null) { + if (ingressTrait.getTlsHosts() == null && !ingressHost.isEmpty()) { + ingressTrait.setTlsHosts(List.of(ingressHost)); + } IngressTLS tls = new IngressTLSBuilder() .withHosts(ingressTrait.getTlsHosts()) .withSecretName(ingressTrait.getTlsSecretName())