Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions documentation/istio_keycloak_integration.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
:toc: macro
toc::[]
:idprefix:
:idseparator: -
== Authentication with Keycloak and Istio

=== Introducing Keycloak
https://www.keycloak.org/[Keycloak] is an opensource IAM (Identity and Access Management) solution with a broad set of features like SSO, authentication and authorization, social login, multifactor authentication etc.
Comment on lines +7 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from our experience in devon4j structuring documentation like this does not work out and is not maintainable.
Therefore my suggestion is:

  • we create separate asciidoc files for keycloak and istio where we can document general aspects for each of them
  • in file like this where we want to document the integration between those two we can link to each of them but shall IMHO not explain anything more but focus on the integration aspects.


=== Introducing Istio
https://istio.io/latest/docs/concepts/[Istio] is an opensource _https://istio.io/latest/about/service-mesh/[Service Mesh]_ that helps organizations run distributed MS-based apps anywhere.
It contains both a control plane and a data plane (Envoy Sidecar). The control plane takes the desired configuration, and its view of the services, and dynamically programs the proxy servers, updating them as the rules or the environment changes. Its powerful features provide a uniform and more efficient way to secure, connect, and monitor services.

* *Traffic management*: managing flow of traffic among services, from external and simplifying configuration of service-level properties like circuit breakers, timeouts etc.
* *Observability*: generating detailed telemetry includes detailed metrics, distributed traces, and full access logs
* *Security*: providing strong identity, powerful policy, transparent TLS encryption, and among-services/external/RBAC authentication, authorization and audit (AAA) tools

== Setting up the Quarkus reference application with Istio and Keycloak

This is a step-by-step guide to setting up the Quarkus reference application in conjunction with Istio and Keycloak. This setup was tested in a local Kubernetes cluster in a WSL environment. To set up the environment, follow these instructions: https://1000kit.gitlab.io/guides/docs/dev-environment/wsl2-pure/.

=== Create the Kubernetes cluster
To create the cluster, we use k3d. First create a local Docker registry.
```
k3d registry create registry --port 5000
```
The next step is to create the cluster. Navigate to the folder with the reference application and run the following command in your WSL environment.
```
k3d cluster create -c k8s/cluster-setup.yaml --k3s-server-arg '--no-deploy=traefik'
```
This will create a local Kubernetes cluster with one master and two worker nodes within your WSL environment. By default, k3d creates a Traefik proxy as an ingress controller. Since we will use the Istio Ingress Gateway later, we do not need it here.
Comment on lines +23 to +31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we really commit ourselves to k3d?
In the PR for devonfw-ide I suggested that we go for docker desktop.
In real environments we will always use kubernetes natively.
So if we locally work with k3d the guide only works locally and only if k3d is present...

I am happy to be told otherwise but why dont we describe this directly with kubectl?


=== Install Istio
The first step is to install Istio. You can find a https://istio.io/latest/docs/setup/getting-started/[Getting Started] guide and some sample applications on the Istio homepage. +
Navigate in your home directory and execute the following commands:
```
curl -L https://istio.io/downloadIstio | sh -
cd istio-1.10.0
export PATH=$PWD/bin:$PATH
```
Comment on lines +33 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to "install" istio natively on our machine?
Dont we run this as container as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further such instruction also does not seem stable and maintainable.
When I was testing the version had meanwhile already changed and I had to do cd istio-1.10.2.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further if we just recommend to follow specific installation instructions from the web, we could simply link them:
https://istio.io/latest/docs/setup/getting-started/

This is already maintained and updated to the latest version.

So we can link instead of copying.

Otherwise we can think about full automation where it really makes sense or integration in devonfw-ide, etc. where suitable.


For this example we use the `default` configuration profile. This will install the Istio core components. We label the Kubernetes namespace 'default' to instruct Istio to inject the Envoy sidecar proxy.
```
istioctl install --set profile=default -y
kubectl label namespace default istio-injection=enabled
```

=== Build the application and create a Docker image
Now we need to build the application and the corresponding Docker image. Use Maven to build the application.
```
mvn clean package
```
If you want to create a native executable use:
```
mvn clean package -Pnative
```
If tests fail because of authorization configurations you can add `-Dmaven.test.skip=true` option to skip the tests.

Then we create a Docker image and push it into your local Docker registry, which was started by k3d.

```
docker build -f src/main/docker/Dockerfile.jvm . -t demo-quarkus
docker tag demo-quarkus k3d-registry:5000/demo-quarkus
Comment on lines +48 to +63
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all this has nothing to do with the integration of keycloak and istio what this documentation should be about.

docker push k3d-registry:5000/demo-quarkus
```

=== Deploy the application in Kubernetes
To deploy the application, apply the following files to your Kubernetes cluster.
```
kubectl apply -f k8s/postgres-deployment.yaml
kubectl apply -f k8s/postgres-service.yaml

kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
```

You now should see a response from the application when using curl to reach the URL http://demo-quarkus.default:8080/animals.
```
kubectl exec $(kubectl get pod -l app=demo-quarkus -o jsonpath={.items..metadata.name}) -c demo-quarkus -- curl http://demo-quarkus.default:8080/animals
Response: {"totalElements":0,"number":0,"size":100,"totalPages":0,"stream":[]}
```

This only works within the Kubernetes cluster, as we use the Kubernetes service name to reach the application. To expose the application for access from outside we can use Istio Gateways:
```
kubectl apply -f k8s/istio/demo-quarkus-gateway.yaml
```
You should now able to open the URL http://demo-quarkus.localhost/animals in your browser.

=== Deploy and configure Keycloak
We use Keycloak for identity management. Apply the following files to your cluster to start a Keycloak instance and expose it via Istio Ingress Gateway for access from outside.
```
kubectl apply -f k8s/keycloak/keycloak.yaml
kubectl apply -f k8s/keycloak/keycloak-gateway.yaml
```

You should be able to open http://keycloak-demo-quarkus.localhost/auth/ in the browser. Open the Keycloak administration console and log in with `admin` as username and password. +
Add a new realm by selecting the realm-export.json file.

Click on `Groups` and create a new group `User`. Then click on `Roles` and create the following roles:
```
admin, user, demo-quarkus.FindObject, demo-quarkus.SaveObject, demo-quarkus.DeleteObject
```
Now create a new user called `demo` and add it to the group you created.
Finally, open the client `demo-quarkus-cli`, click on the tab Mapper and add the group mapper to the client. Your Keyloak is now fully configured.

=== Test the application
Now it is time to test the application. Run the following command:
```
kubectl exec $(kubectl get pod -l app=demo-quarkus -o jsonpath={.items..metadata.name}) -c demo-quarkus -- curl http://demo-quarkus.default:8080/animals
```
You should get a valid response from the application. But there are no animals in our database at the moment. So let's try to create an animal. To do this, run the following command:
```
kubectl exec $(kubectl get pod -l app=demo-quarkus -o jsonpath={.items..metadata.name}) -c demo-quarkus -- curl -H "Content-Type: application/json" --request POST --data '{"name": "dog", "basicInfo": "home pet", "numberOfLegs":4}' http://demo-quarkus.default:8080/animals -i
```
You will get an `401 Unauthorized` error message. This is because this operation is secured with the role `demo-quarkus.SaveObject`. You can only access this operation if you pass a valid JWT token in the request header. So add the role to the user in Keycloak and run the following command to get the token.
```
TOKEN=$(curl -d 'client_id=demo-quarkus-cli' -d 'username=demo' -d 'password=demo' -d 'grant_type=password' 'http://keycloak-demo-quarkus.localhost/auth/realms/demo-quarkus/protocol/openid-connect/token' | jq ".access_token" -r)
Copy link
Contributor

@lilliCao lilliCao Jun 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request token with client secret too. 'client_secret=7745de1c-a85e-4bfe-97fe-42dc2b0b3f65'. Without that, istio will return Jwks doesn't have key to match kid or alg from Jwt if authorization_policy.yaml is deployed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You only need to pass the client secret if 'Access Type' of the Keycloak client is set to confidential. In the json file of the realm, the client 'demo-quarkus-cli' is specified as public client.

```
Now you can call the operation again and this time pass the token:
```
kubectl exec $(kubectl get pod -l app=demo-quarkus -o jsonpath={.items..metadata.name}) -c demo-quarkus -- curl -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" --request POST --data '{"name": "dog", "basicInfo": "home pet", "numberOfLegs":4}' http://demo-quarkus.default:8080/animals -i
```
There is now an animal stored in the database. You can check this by displaying the list of animals again. +
To use the other methods implemented in the application to find and delete animals, you need to add the roles `demo-quarkus.FindObject` and `demo-quarkus.DeleteObject` to the user and get a new token.

=== Authorization by Istio
You can also add authorization policies with Istio. Requests are then first validated by the Istio service mesh before being forwarded to the application. +
Add the authorization policy by applying the file `k8s/istio/authorization-policy.yaml` to your cluster.
```
kubectl apply -f k8s/istio/authorization-policy.yaml
```
Now try again to get the list of animals. You will get an `RBAC: access denied` error message. This is because the url http://demo-quarkus.default:8080/animals is now also protected by a Istio policy. You need to pass a valid JWT token with the role 'user'. So add the role 'user' to the user in keycloak, get a new token and try again. +
Now you should get a valid response.
```
kubectl exec $(kubectl get pod -l app=demo-quarkus -o jsonpath={.items..metadata.name}) -c demo-quarkus -- curl http://demo-quarkus.default:8080/animals -H "Authorization: Bearer $TOKEN"
```

=== When to use which authorization?
Normally, the Istio authorisation policies are sufficient to provide standard role bases permissions. In this case, you do not need to validate the token again in the application. +
If you want to add further validation, such as checking the claims of the JWT token or dynamically adding permissions based on database entries, then it is better to add further authorization logic in the code of the application.

=== Securing the gateways with HTTPS
At the moment, external traffic from the client to the service is not secured. Istio provides secure gateways to host the services on HTTPS using simple or mutual TLS.

First we generate a self-signed root certificate for the services.
```
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=Demo Inc./CN=demo.com' -keyout root-cert.key -out root-cert.crt
```
In the next step we create a certificate and a private key for `demo-quarkus.localhost`. We first generate a private key and a CSR (Certificate Signing Request) and then use the root certificate to sign the CSR and create the certificate.
```
openssl req -out demo-quarkus.csr -newkey rsa:2048 -nodes -keyout demo-quarkus.key -subj "/CN=demo-quarkus.localhost/O=demo organization"
openssl x509 -req -days 365 -CA root-cert.crt -CAkey root-cert.key -set_serial 0 -in demo-quarkus.csr -out demo-quarkus.crt
```
Now we need to create a TLS secret with the generated key and certificate.
```
kubectl create -n istio-system secret tls demo-quarkus-credential --key=demo-quarkus.key --cert=demo-quarkus.crt
```
Apply the file `demo-quarkus-gateway-secure.yaml` to your cluster. The file defines a gateway listening on port 443 with a simple TLS protocol. The name of the credential in the yaml file must match the name of the secret you created earlier.
Now you should be able to reach the service and get the correct response by passing the root certificate in the curl command.
```
export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
curl --resolve "demo-quarkus.localhost:443:$INGRESS_HOST" --cacert root-cert.crt "https://demo-quarkus.localhost:443/animals"
```
If you execute the command without the `--cacert` option, the command will fail because a secure connection cannot be established. You can use the `-v`(verbose) option of the curl command to get detailed information.
{empty} +
{empty} +

==== Mututal TLS
In the next step, we extend our gateway to support https://en.wikipedia.org/wiki/Mutual_authentication[mutual TLS]. This may be necessary if you need to establish trust between the client and the server and vice versa.
To do this, change the TLS mode in the gateway file from `SIMPLE` to `MUTUAL` and apply it again to the Kubernetes cluster. Try the curl command from the previous step again. It will not work because the server now also expects a certificate from the client.

First delete the old tls secret.
```
kubectl -n istio-system delete secret demo-quarkus-credential
```
Now create a new secret. You must set the properties `tls.key` and `tls.crt`, and `ca.crt` to include the CA certificate (root certificate).
```
kubectl create -n istio-system secret generic demo-quarkus-credential --from-file=tls.key=demo-quarkus.key --from-file=tls.crt=demo-quarkus.crt --from-file=ca.crt=root-cert.crt
```
Create the client certificate and sign it with the root certificate.
```
openssl req -out client.csr -newkey rsa:2048 -nodes -keyout client.key -subj "/CN=client.com/O=client organization"
openssl x509 -req -days 365 -CA root-cert.crt -CAkey root-cert.key -set_serial 1 -in client.csr -out client.crt
```
When passing the client certificate and private key to the curl command, you should be able to see a valid response.
```
curl --resolve "demo-quarkus.localhost:443:$INGRESS_HOST" --cacert root-cert.crt --cert client.crt --key client.key "https://demo-quarkus.localhost:443/animals"
```

==== Internal traffic
By default, a service's sidecar proxy is configured to accept both mTLS and non-mTLS traffic (`PERMISSIVE` mode). This can be changed by using a `PeerAuthentication` resource. Use `STRICT` mode to force traffic to use mTLS, or `DISABLE` mode where traffic must be in plain text.
```
kubectl apply -n foo -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
spec:
mtls:
mode: DISABLE
EOF
```
You can use the Grafana dashboard to check whether plain text is sent or not.

== Istio dashboards
Istio provides various dashboards for monitoring and visualising different aspects of your Istio network. Create the monitor applications by applying the `addons` folder to your Kubernetes cluster.
```
kubectl apply -f k8s/istio/addons/
```
Once the pods have been created, you can launch the dashboard application by running:
```
istioctl dashboard dashboardName
```
2 changes: 1 addition & 1 deletion reference-project/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ It is recommended to use vanilla maven settings(no custom mirror, proxy) for bet
Create your k3d cluster and registry
```shell
k3d registry create registry --port 5000
k3d cluster create -c k8s/dev.yaml
k3d cluster create -c k8s/cluster-setup.yaml
```

Package your app as docker container and push to local k3d registry:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ apiVersion: k3d.io/v1alpha2
kind: Simple
name: dev
servers: 1
agents: 2
ports:
- port: 80:80
nodeFilters:
- loadbalancer
registries:
use: k3d-registry:5000
use: k3d-registry:5000
6 changes: 6 additions & 0 deletions reference-project/k8s/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ spec:
value: demo
- name: QUARKUS_DATASOURCE_PASSWORD
value: demo
- name: MP_JWT_VERIFY_ISSUER
value: "http://keycloak-demo-quarkus.localhost/auth/realms/demo-quarkus"
- name: MP_JWT_VERIFY_PUBLICKEY_LOCATION
value: "http://keycloak.default:8080/auth/realms/demo-quarkus/protocol/openid-connect/certs"
- name: QUARKUS_HIBERNATE_ORM_DATABASE_GENERATION
value: drop-and-create
# live & ready probes, using our healthcheck endpoints
livenessProbe:
failureThreshold: 5
Expand Down
Loading