APIcast icon indicating copy to clipboard operation
APIcast copied to clipboard

[THREESCALE 11019] - FAPI advance profile

Open tkan145 opened this issue 1 year ago • 0 comments

What

This PR support https://issues.redhat.com/browse/THREESCALE-11019.

Verification steps:

  • Build new runtime-image
make runtime-image IMAGE_NAME=apicast-test
  • Move into keycloak dev-environment
cd dev-environments/keycloak-env

Prepare certificates

mkdir certs && cd certs

Generate CA certificate

openssl genrsa -out rootCA.key 2048
openssl req -batch -new -x509 -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem
  • Generate a private key for the HTTPS keystore. Provide changeit as keystore password
keytool -genkeypair -keyalg RSA -keysize 2048 -dname "CN=keycloak" -alias jboss -keystore keystore.jks -storepass changeit -keypass changeit

Generate a certificate signing request (CSR) for the HTTPS keystore.

keytool -certreq -keyalg rsa -alias jboss -keystore keystore.jks -file sso.csr -storepass changeit
  1. Sign the CSR with the CA certificate..
openssl x509 -req -extfile  <(printf "subjectAltName=DNS:keycloak") -CA rootCA.pem -CAkey rootCA.key -in sso.csr -out sso.crt -days 365 -CAcreateserial
  1. Import the CA certificate into the HTTPS keystore. Reply yes to Trust this certificate? [no]: question
keytool -import -file rootCA.pem -alias rootCA.ca -keystore keystore.jks -storepass changeit -noprompt
  1. Import the signed CSR into the HTTPS keystore.
keytool -import -file sso.crt -alias jboss -keystore keystore.jks -storepass changeit

We can verify the certificates are imported with below command.

keytool -v -list -storepass changeit \  
  -alias jboss -keystore keystore.jks
  1. Import CA certificate into the truststore
keytool -import -file rootCA.pem -alias rootCA.ca -keystore truststore.jks -storepass changeit -noprompt

Generate client certificate

Generate APIcast certificates

$ openssl req -subj '/CN=apicast'  -newkey rsa:4096 -nodes \
      -sha256 \
      -days 3650 \
      -keyout apicast.key \
      -out apicast.csr
$ chmod +r apicast.key
$ openssl x509 -req -in apicast.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out apicast.crt -days 500 -sha256
$ cat apicast.key apicast.crt >apicast.pem

Repeat the step above and generate the client certificate

$ openssl req -subj '/CN=client'  -newkey rsa:4096 -nodes \
      -sha256 \
      -days 3650 \
      -keyout client.key \
      -out client.csr
$ chmod +r client.key
$ openssl x509 -req -in client.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out client.crt -days 500 -sha256
$ cat client.key client.crt >client.pem

Once the certificate are generated, we are ready to deploy Keycloak

Deploy

  • Update apicast-config.json
diff --git a/dev-environments/keycloak-env/apicast-config.json b/dev-environments/keycloak-env/apicast-config.json
index 071296cd..bab21204 100644
--- a/dev-environments/keycloak-env/apicast-config.json
+++ b/dev-environments/keycloak-env/apicast-config.json
@@ -61,7 +61,7 @@
         "api_test_path": "/",
         "api_test_success": null,
         "apicast_configuration_driven": true,
-        "oidc_issuer_endpoint": "http://oidc-issuer-for-3scale:oidc-issuer-for-3scale-secret@keycloak:8080/realms/basic",
+        "oidc_issuer_endpoint": "https://oidc-issuer-for-3scale:oidc-issuer-for-3scale-secret@keycloak:8443/realms/basic",
         "lock_version": 4,
         "authentication_method": "oidc",
         "oidc_issuer_type": "keycloak",
@@ -84,10 +84,10 @@
         },
         "policy_chain": [
           {
-            "name": "token_introspection",
+            "name": "fapi",
             "version": "builtin",
             "configuration": {
-              "auth_type": "use_3scale_oidc_issuer_endpoint"
+              "validate_oauth2_certificate_bound_access_token": true
             }
           },
           {
  • Update docker-compose.yaml with the following
diff --git a/dev-environments/keycloak-env/docker-compose.yml b/dev-environments/keycloak-env/docker-compose.yml
index 3fdbd011..a0904272 100644
--- a/dev-environments/keycloak-env/docker-compose.yml
+++ b/dev-environments/keycloak-env/docker-compose.yml
@@ -8,6 +8,9 @@ services:
     - two.upstream
     - keycloak
     environment:
+      APICAST_HTTPS_PORT: 8443
+      APICAST_HTTPS_CERTIFICATE: /var/run/secrets/apicast/apicast.crt
+      APICAST_HTTPS_CERTIFICATE_KEY: /var/run/secrets/apicast/apicast.key
       THREESCALE_CONFIG_FILE: /tmp/config.json
       THREESCALE_DEPLOYMENT_ENV: staging
       APICAST_CONFIGURATION_LOADER: lazy
@@ -22,6 +25,7 @@ services:
       - "8090:8090"
     volumes:
       - ./apicast-config.json:/tmp/config.json
+      - ./certs:/var/run/secrets/apicast
   example.com:
     image: alpine/socat:1.7.4.4
     container_name: example.com
@@ -39,9 +43,20 @@ services:
     command: "start-dev"
     expose:
       - "8080"
+      - "8443"
     ports:
       - "9090:8080"
+      - "9443:8443"
+    volumes:
+      - ./certs/keystore.jks:/etc/x509/https/keystore.jks
+      - ./certs/truststore.jks:/etc/x509/https/truststore.jks
+      - ./keycloak_mtls.json:/tmp/keycloak_mtls.json
     restart: unless-stopped
     environment:
       KEYCLOAK_ADMIN: admin
       KEYCLOAK_ADMIN_PASSWORD: adminpass
+      KC_HTTPS_CLIENT_AUTH: request
+      KC_HTTPS_KEY_STORE_FILE: /etc/x509/https/keystore.jks
+      KC_HTTPS_KEY_STORE_PASSWORD: changeit
+      KC_HTTPS_TRUST_STORE_FILE: /etc/x509/https/truststore.jks
+      KC_HTTPS_TRUST_STORE_PASSWORD: changeit
(END)
  • Bootstrap keycloak
make keycloak-data
  • We should be able to access keycloak UI via https://localhost:9443
  • Login as admin user admin:adminpass
  • Switch to basic realm
  • Create new client Clients -> Create client
Client type: OpenID Connect
Client ID: mtls_client_demo
  • Click Next
Client authentication: On
Authorization: On
  • Click Next -> then Save
  • Once the new client is created, go to Credentials tab and select X509 Certificate as Client Authenticator
  • Next put in your Subject DN, in this test we use (.*?)(?:$). Then click Save
  • Go to Advanced tab, and then scroll down to Advance settings and enable OAuth 2.0 Mutual TLS Certificate Bound Access Token -> click Save

Request token

Now we have everything set up, we can use curl to authenticate with the client certificate to get the access token.

ACCESS_TOKEN=$(docker compose -p keycloak-env exec gateway curl -k -v -H "Content-Type: application/x-www-form-urlencoded" \
   -d 'grant_type=client_credentials' \
   -d 'client_id=mtls_client_demo' \
   --cert /var/run/secrets/apicast/client.crt \
   --key /var/run/secrets/apicast/client.key \
   --cacert /var/run/secrets/apicast/rootCA.pem \
   "https://keycloak:8443/realms/basic/protocol/openid-connect/token" | jq -r '.access_token')
  • Validate that token has cnf claim. If we decode this token using jwt.io, we can see the payload has the cnf claim and its value is certificate sha256 thumbprint. The resource server (APIcast) will use this thumbprint to validate if the same user is making the request. For example:
"cnf": {
    "x5t#S256": "3hhTJwX93ZWWyuuKOzm1k4qo-MH0dfhDC7jgg8ZyR6U"
  },

Now we are ready to test

  • Get APIcast IP
APICAST_IP=$(docker inspect keycloak-env-gateway-1 | yq e -P '.[0].NetworkSettings.Networks.keycloak-env_default.IPAddress' -)
  • Send request
curl -v --resolve stg.example.com:8080:127.0.0.1 -H "Authorization: Bearer ${ACCESS_TOKEN}" "http://stg.example.com:8080"

Request should failed with {"error": "invalid_token"}

  • Send request again with client certificates
curl -v -k -H "Host: stag.example.com" -H "Authorization: Bearer ${ACCESS_TOKEN}" \
   --cert certs/client.crt \
   --key certs/client.key \
  "https://${APICAST_IP}:8443"

Request should return 200

< HTTP/2 200
< server: openresty
< date: Fri, 14 Jun 2024 06:50:11 GMT
< content-type: application/json
< content-length: 1756
< access-control-allow-origin: *
< access-control-allow-credentials: true
< x-fapi-transaction-id: 83688fb6-0ca9-44d3-9e9f-3b31bcdefd8a
<

tkan145 avatar Jun 07 '24 12:06 tkan145