diff --git a/components/camel-oauth/helm/README.md b/components/camel-oauth/helm/README.md index ba8bc3d3312f9..fe8283d5e6940 100644 --- a/components/camel-oauth/helm/README.md +++ b/components/camel-oauth/helm/README.md @@ -1,6 +1,6 @@ # Local Kubernetes Cluster -To keep the entry barrier for Camel OAuth low, we initially deploy Keycloak as our Identity Provider on Docker Desktop Kubernetes. +To keep the entry barrier for Camel OAuth low, we initially deploy Keycloak as our Identity Provider on Rancher Desktop Kubernetes. This is a single node Kubernetes cluster running on localhost. ## Ingress with Traefik @@ -9,45 +9,32 @@ Keycloak should only be accessed with transport layer security (TLS) in place. T of exchanging privacy/security sensitive data over any channel. Here we place Keycloak behind a TLS terminating proxy (Traefik). It has the advantage that any traffic -(i.e. not only for Keycloak) can be secured at ingress level. +(i.e. not only for Keycloak) can be secured at ingress level. Traefik should already be installed with Rancher Desktop. https://doc.traefik.io/traefik/ -``` -helm repo add traefik https://traefik.github.io/charts -helm repo update -helm install traefik traefik/traefik -``` - -Once Traefik is installed, we create a Kubernetes TLS 'secret'. -In case you'd like to regenerate the TLS certificate and key, do this ... -Also, a Java app that wants to access Keycloak over TLS, must trust that certificate. +Create and install TLS edge certificate ``` -# Generate TLS Certificate -openssl req -x509 -newkey rsa:4096 -keyout ./helm/etc/cluster.key -out ./helm/etc/cluster.crt -days 365 -nodes -config ./helm/etc/san.cnf +brew install mkcert nss -# Show Certificate -cat ./helm/etc/cluster.crt | openssl x509 -noout -text +# Make sure the mkcert root CA is trusted +mkcert --install -# Import TLS Certificate to Java Keystore (i.e. trust the certificate) -sudo keytool -import -alias camel-oauth -file ./helm/etc/cluster.crt -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit +mkcert "localtest.me" "*.localtest.me" +mkdir -p helm/tls && mv localtest.* helm/tls -# Remove TLS Certificate from Java Keystore -sudo keytool -delete -alias camel-oauth -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit +kubectl delete secret edge-tls --ignore-not-found=true +kubectl create secret tls edge-tls \ + --cert=helm/tls/localtest.me+1.pem \ + --key=helm/tls/localtest.me+1-key.pem -# Trust this cert on macOS -sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ./helm/etc/cluster.crt - -# Trust this cert on Rock9 -sudo cp ./helm/etc/cluster.crt /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust -``` - -Once we have the TLS certificate, we can install the TLS secret like this ... - -``` -helm upgrade --install traefik-tls ./helm -f ./helm/values-traefik-tls.yaml +# The above shold also install the root cert with the Java system truststore. This is matter of mkcert finding the correct JRE home. +# In case it does not, you may need to import the mkcert rootCA.pem manually to the Java truststore. +keytool -delete -cacerts -alias mkcert-root -storepass changeit +keytool -importcert -cacerts -alias mkcert-root -storepass changeit -noprompt \ + -file "$(mkcert -CAROOT)/rootCA.pem" ``` ... and verify that TLS access is working @@ -56,13 +43,12 @@ helm upgrade --install traefik-tls ./helm -f ./helm/values-traefik-tls.yaml helm upgrade --install whoami ./helm -f ./helm/values-whoami.yaml ``` -https://example.local/who +https://localtest.me/who -Note, the domains `example.local` and `keycloak.local` are mapped to an actual IP in `/etc/hosts`. ## Installing Keycloak -Using Helm, we can install a pre-configured instance of Keycloak behind Traefik like this ... +Using Helm, we can also install a pre-configured instance of Keycloak behind Traefik like this ... ``` helm upgrade --install keycloak ./helm -f ./helm/values-keycloak.yaml \ @@ -72,7 +58,7 @@ helm upgrade --install keycloak ./helm -f ./helm/values-keycloak.yaml \ helm uninstall keycloak ``` -https://keycloak.local/kc +https://oauth.localtest.me Admin: admin/admin User: alice/alice @@ -86,7 +72,7 @@ Note, in case you see `NoSuchAlgorithmException: RSA-OAEP`, we can disable that Create realm 'camel' if not already imported ``` -kcadm config credentials --server https://keycloak.local/kc --realm master --user admin --password admin +kcadm config credentials --server https://oauth.localtest.me/kc --realm master --user admin --password admin kcadm create realms -s realm=camel -s enabled=true @@ -137,59 +123,6 @@ helm upgrade --install kafka ./helm -f ./helm/values-kafka.yaml \ helm uninstall kafka ``` -# Remote Kubernetes Cluster - -Next level up, we run a single node cluster that we access remotely - [K3S](https://k3s.io/) is an excellent choice for that. - -Once K3s is running, we can use [Lens](https://k8slens.dev/), [kubectx](https://github.com/ahmetb/kubectx) or plain `kubectl config` for context switching to k3s. - -As above, we need to install the TLS secret - -``` -helm upgrade --install traefik-tls ./helm -f ./helm/values-traefik-tls.yaml -``` - -... and then Keycloak - -``` -helm upgrade --install keycloak ./helm -f ./helm/values-keycloak.yaml \ - && kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=keycloak --timeout=20s \ - && kubectl logs --tail 400 -f -l app.kubernetes.io/name=keycloak - -helm uninstall keycloak -``` - -https://keycloak.k3s/kc - -## Modifying CoreDNS - -Unlike DockerDesktop Kubernetes, pods deployed on K3S do not see /etc/hosts from the host system. Instead, K3S uses -CoreDNS to resolve host names, which we can use to add the required mapping. - -``` -kubectl -n kube-system edit configmap coredns - - Corefile: | - .:53 { - ... - hosts /etc/coredns/NodeHosts { - keycloak.k3s - ttl 60 - reload 15s - fallthrough - } -``` - -Please let us know, when there is a better way to provide a host mapping such that traffic goes through the Keycloak -IngressRoute, which references our custom TLS certificate. - -## Private Registry - -Most of our examples reference images that are deployed to the private registry of the given cluster (i.e. these images -are not available in public registries). [camel-cloud-examples](https://github.com/tdiesler/camel-cloud-examples/tree/main) -provides [Ansible playbooks](https://github.com/tdiesler/camel-cloud-examples/tree/main/ansible) that show how ton install -a private registry in K3S. There is also some documentation in K3S [directly](https://docs.k3s.io/installation/private-registry). - # OpenShift First, we create a new project on the OpenShift cluster diff --git a/components/camel-oauth/helm/etc/camel-realm.json b/components/camel-oauth/helm/etc/camel-realm.json index 281d6960253a6..23aa1447e3d43 100644 --- a/components/camel-oauth/helm/etc/camel-realm.json +++ b/components/camel-oauth/helm/etc/camel-realm.json @@ -168,11 +168,12 @@ "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": "https://example.local/##https://example.k3s/" + "post.logout.redirect.uris": "http://127.0.0.1:8080/##https://example.local/##https://example.k3s/" } }, { diff --git a/components/camel-oauth/helm/etc/san.cnf b/components/camel-oauth/helm/etc/san.cnf deleted file mode 100644 index ee2a9ce5ab037..0000000000000 --- a/components/camel-oauth/helm/etc/san.cnf +++ /dev/null @@ -1,25 +0,0 @@ -[ req ] -default_bits = 4096 -distinguished_name = req_distinguished_name -x509_extensions = v3_req -prompt = no - -[ req_distinguished_name ] -C = US -ST = State -L = City -O = Organization -OU = Unit -CN = Camel Cloud Examples - -[ v3_req ] -keyUsage = critical, digitalSignature, keyEncipherment -extendedKeyUsage = serverAuth -subjectAltName = @alt_names - -[ alt_names ] -DNS.1 = example.local -DNS.2 = example.k3s -DNS.3 = keycloak.local -DNS.4 = keycloak.k3s -IP.1 = 127.0.0.1 diff --git a/components/camel-oauth/helm/templates/keycloak.yaml b/components/camel-oauth/helm/templates/keycloak.yaml index 5d3809f20b3ce..a7d7b9b40647e 100644 --- a/components/camel-oauth/helm/templates/keycloak.yaml +++ b/components/camel-oauth/helm/templates/keycloak.yaml @@ -111,13 +111,13 @@ spec: entryPoints: - websecure routes: - - match: PathPrefix(`/kc`) + - match: Host("{{ .apiHost }}") kind: Rule services: - name: keycloak port: 8080 tls: - secretName: traefik-tls + secretName: edge-tls --- {{- end }} # environment == 'k8s' diff --git a/components/camel-oauth/helm/templates/traefik-tls.yaml b/components/camel-oauth/helm/templates/traefik-tls.yaml deleted file mode 100644 index 9763ed228ae70..0000000000000 --- a/components/camel-oauth/helm/templates/traefik-tls.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -{{- with .Values.deployments.traefik }} - -# Traefik TLS Secret ---- -apiVersion: v1 -kind: Secret -metadata: - name: traefik-tls - labels: - app.kubernetes.io/name: traefik - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/release: {{ $.Release.Name }} - app.kubernetes.io/revision: '{{ $.Release.Revision }}' -type: kubernetes.io/tls -data: - tls.crt: {{ $.Files.Get "etc/cluster.crt" | b64enc }} - tls.key: {{ $.Files.Get "etc/cluster.key" | b64enc }} - ---- -{{- end }} diff --git a/components/camel-oauth/helm/templates/whoami.yaml b/components/camel-oauth/helm/templates/whoami.yaml index 87706a44d5443..ccef00bec548b 100644 --- a/components/camel-oauth/helm/templates/whoami.yaml +++ b/components/camel-oauth/helm/templates/whoami.yaml @@ -79,7 +79,7 @@ spec: middlewares: - name: strip-prefix-who tls: - secretName: traefik-tls + secretName: edge-tls --- apiVersion: traefik.io/v1alpha1 diff --git a/components/camel-oauth/helm/values-keycloak.yaml b/components/camel-oauth/helm/values-keycloak.yaml index c7f582a5807a4..f0c7bac4cf091 100644 --- a/components/camel-oauth/helm/values-keycloak.yaml +++ b/components/camel-oauth/helm/values-keycloak.yaml @@ -19,5 +19,6 @@ environment: k8s deployments: keycloak: - version: 26.1.2 + apiHost: "oauth.localtest.me" + version: 26.4.2 diff --git a/components/camel-oauth/helm/values-traefik-tls.yaml b/components/camel-oauth/helm/values-traefik-tls.yaml deleted file mode 100644 index c570dfa052556..0000000000000 --- a/components/camel-oauth/helm/values-traefik-tls.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# -# helm upgrade --install traefik-tls ./helm -f ./helm/values-traefik-tls.yaml -# - -deployments: - - traefik: - name: "traefik-tls" diff --git a/components/camel-oauth/src/main/docs/oauth-component.adoc b/components/camel-oauth/src/main/docs/oauth-component.adoc index 9dba93bfd362e..4c95d4d66b5a8 100644 --- a/components/camel-oauth/src/main/docs/oauth-component.adoc +++ b/components/camel-oauth/src/main/docs/oauth-component.adoc @@ -63,7 +63,7 @@ For details see the https://openid.net/specs/openid-connect-core-1_0.html[OIDC 1 |=== |Name |Description -|`camel.oauth.base-uri` |The base URL to the identity provider (e.g. https://keycloak.local/kc/realms/camel) +|`camel.oauth.base-uri` |The base URL to the identity provider (e.g. https://oauth.localtest.me/kc/realms/camel) |`camel.oauth.redirect-uri` |Valid URI pattern a browser can redirect to after a successful login (e.g. http://127.0.0.1:8080/auth). Must be registered with the identity provider. @@ -88,7 +88,7 @@ For details see the https://datatracker.ietf.org/doc/html/rfc6749#section-4.4[OA |=== |Name |Description -|`camel.oauth.base-uri` |The base URL to the identity provider (e.g. https://keycloak.local/kc/realms/camel) +|`camel.oauth.base-uri` |The base URL to the identity provider (e.g. https://oauth.localtest.me/kc/realms/camel) |`camel.oauth.client-id` |The client identifier registered with the identity provider. @@ -102,7 +102,7 @@ Naturally, we want all communication between camel and the identity provider to [source,shell] ---- # Fetch the certificate from the IdP endpoint -openssl s_client -connect keycloak.local:443 | openssl x509 > cluster.crt +openssl s_client -connect oauth.localtest.me:443 | openssl x509 > cluster.crt # Import certificate to Java Keystore (i.e. trust the certificate) sudo keytool -import -alias keycloak -file cluster.crt -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit @@ -150,13 +150,13 @@ For example ... ---- k8s-fetch-cert: @mkdir -p tls - @echo -n | openssl s_client -connect keycloak.local:443 | openssl x509 > tls/cluster.crt + @echo -n | openssl s_client -connect oauth.localtest.me:443 | openssl x509 > tls/cluster.crt k8s-export: k8s-fetch-cert @$(CAMEL_CMD) kubernetes export platform-http-files/* tls/* \ --dep=org.apache.camel:camel-oauth:4.16.0-SNAPSHOT \ --gav=examples:platform-http-oauth:1.0.0 \ - --property=camel.oauth.base-uri=https://keycloak.local/kc/realms/camel \ + --property=camel.oauth.base-uri=https://oauth.localtest.me/kc/realms/camel \ --property=camel.oauth.redirect-uri=http://127.0.0.1:8080/auth \ --property=camel.oauth.logout.redirect-uri=http://127.0.0.1:8080/ \ --property=camel.oauth.client-id=camel-client \ diff --git a/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java b/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java index 440aa64e5b30c..06c756ad2eda5 100644 --- a/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java +++ b/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java @@ -179,44 +179,36 @@ private static JsonObject verifyToken(OAuthConfig config, String token, boolean var payload = signedJWT.getPayload().toString(); tokenJwt = JsonParser.parseString(payload).getAsJsonObject(); - var target = new ArrayList(); + var targetAudience = new ArrayList(); var jwtOptions = config.getJWTOptions(); if (tokenJwt.has("aud")) { try { var aud = tokenJwt.get("aud"); if (aud.isJsonPrimitive()) { - target.add(aud.getAsString()); + targetAudience.add(aud.getAsString()); } else { - target.addAll(aud.getAsJsonArray().asList().stream().map(JsonElement::getAsString).toList()); + targetAudience.addAll(aud.getAsJsonArray().asList().stream().map(JsonElement::getAsString).toList()); } } catch (RuntimeException ex) { throw new OAuthException("User audience isn't a JsonArray or String"); } } - if (!target.isEmpty()) { - if (!idToken && jwtOptions.getAudience() != null) { - for (String el : jwtOptions.getAudience()) { - if (!target.contains(el)) { - throw new OAuthException("Invalid JWT audience. expected: " + el); - } - } - } else if (!target.contains(config.getClientId())) { - throw new OAuthException("Invalid JWT audience. expected: " + config.getClientId()); + String clientId = config.getClientId(); + if (idToken && !targetAudience.contains(clientId)) { + throw new OAuthException("Invalid JWT audience. Expected to contain: " + clientId); + } + if (idToken && tokenJwt.has("azp")) { + String authorizedParty = tokenJwt.get("azp").getAsString(); + if (!authorizedParty.equals(clientId)) { + throw new OAuthException("Invalid authorized party != config.clientID"); + } + if (!targetAudience.contains(authorizedParty)) { + throw new OAuthException("ID Token audience, doesn't contain authorized party"); } } var issuer = jwtOptions.getIssuer(); if (issuer != null && !issuer.equals(tokenJwt.get("iss").getAsString())) { throw new OAuthException("Invalid JWT issuer"); - } else { - if (idToken && tokenJwt.has("azp")) { - String clientId = config.getClientId(); - if (!clientId.equals(tokenJwt.get("azp").getAsString())) { - throw new OAuthException("Invalid authorised party != config.clientID"); - } - if (!target.isEmpty() && !target.contains(tokenJwt.get("azp").getAsString())) { - throw new OAuthException("ID Token with multiple audiences, doesn't contain azp Claim value"); - } - } } } catch (OAuthException ex) { throw ex; diff --git a/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/AbstractKeycloakTest.java b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/AbstractKeycloakTest.java index 0e147a3b1142c..b42c9f3bfce2a 100644 --- a/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/AbstractKeycloakTest.java +++ b/components/camel-oauth/src/test/java/org/apache/camel/test/oauth/AbstractKeycloakTest.java @@ -36,8 +36,8 @@ abstract class AbstractKeycloakTest { static final String APP_BASE_URL = "http://127.0.0.1:" + port + "/"; static final String KEYCLOAK_REALM = "camel"; - static final String KEYCLOAK_BASE_URL = "https://keycloak.local/kc/"; - static final String KEYCLOAK_REALM_URL = KEYCLOAK_BASE_URL + "realms/" + KEYCLOAK_REALM; + static final String KEYCLOAK_BASE_URL = "https://oauth.localtest.me/kc"; + static final String KEYCLOAK_REALM_URL = KEYCLOAK_BASE_URL + "/realms/" + KEYCLOAK_REALM; static final String TEST_CLIENT_ID = "camel-client"; static final String TEST_CLIENT_SECRET = "camel-client-secret"; diff --git a/components/camel-oauth/src/test/resources/index.html b/components/camel-oauth/src/test/resources/index.html index 28ffec3ee19b6..265ea6453baf2 100644 --- a/components/camel-oauth/src/test/resources/index.html +++ b/components/camel-oauth/src/test/resources/index.html @@ -29,7 +29,7 @@

Web App

All users can see this.

Home Protected -Keycloak +Keycloak Logout diff --git a/components/camel-oauth/src/test/resources/protected.html b/components/camel-oauth/src/test/resources/protected.html index b23989db2d3bd..1e9e6cbc40a03 100644 --- a/components/camel-oauth/src/test/resources/protected.html +++ b/components/camel-oauth/src/test/resources/protected.html @@ -29,7 +29,7 @@

Secured Web App

Only authenticated users can see this.

Home Protected -Keycloak +Keycloak Logout