APIcast
APIcast copied to clipboard
[THREESCALE 11019] - FAPI advance profile
What
This PR support https://issues.redhat.com/browse/THREESCALE-11019.
- shall support the provisions specified in clause 6.2.1 Financial-grade API Security Profile 1.0 - Part 1: Baseline; and
- shall adhere to the requirements in MTLS.
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
changeitas 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
- 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
- Import the CA certificate into the HTTPS keystore. Reply
yestoTrust this certificate? [no]:question
keytool -import -file rootCA.pem -alias rootCA.ca -keystore keystore.jks -storepass changeit -noprompt
- 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
- 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
basicrealm - 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
Credentialstab and selectX509 CertificateasClient Authenticator - Next put in your
Subject DN, in this test we use(.*?)(?:$). Then clickSave - Go to
Advancedtab, and then scroll down toAdvance settingsand enableOAuth 2.0 Mutual TLS Certificate Bound Access Token-> clickSave
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
cnfclaim 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
<