Skip to content

Commit

Permalink
authN-authZ: add documentation and configs for oauth2
Browse files Browse the repository at this point in the history
* add the yamls for configurations of oauth2
* add oauth2-proxy installation guide
* update documentations

Signed-off-by: Ruoyu Ying <[email protected]>
  • Loading branch information
Ruoyu-y committed Aug 15, 2024
1 parent 919e492 commit 2b12a68
Show file tree
Hide file tree
Showing 19 changed files with 390 additions and 6 deletions.
132 changes: 128 additions & 4 deletions microservices-connector/config/authN-authZ/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Use GMC and Istio to compose an OPEA Pipeline with authentication and authorization enabled

In enterprise settings not only do we want to identify who is using a service but also what they are entitled to use. This is where authentication and authorization comes in. In contrast, API tokens provide full access by virtue of possession as long as they are valid/not expired. With that aside, lets dive into how we can accomplish AuthN and AuthZ in OPEA using Istio and JWT tokens.
In enterprise settings not only do we want to identify who is using a service but also what they are entitled to use. This is where authentication and authorization comes in. In contrast, API tokens provide full access by virtue of possession as long as they are valid/not expired. With that aside, we first provide the solution on AuthN and AuthZ in OPEA using Istio and JWT tokens. Another option is to leverage the oauth2-proxy with various OIDC providers for authentication and authorization. Using oauth2-proxy with Istio ensures secure, scalable access control, centralizes user management, and provides seamless single sign-on capabilities, improving overall security and user experience in complex microservices environments.

Currently we provide two options to implement the task : via fake JWT token and via JWT token generated by OIDC providers. And here we use the chatQnA pipeline as an example.

Currently we provide three kinds of setups for authentication and authorization: via fake JWT token, via JWT token generated by OIDC providers and via oauth2-proxy and OIDC providers. And here we use the chatQnA pipeline as an example.

## Prerequisite

Expand All @@ -29,7 +30,15 @@ kubectl patch deployment -n chatqa router-server --patch '{
}'
```

## Perform authentication and authorization via fake JWT tokens
The istio ingress gateway will be used to access the chatQnA service in different setups. Follow the istio guide [here](https://istio.io/latest/docs/tasks/traffic-management/ingress/ingress-control/#determining-the-ingress-ip-and-ports) to determine the ingress IP and ports.

## Perform authentication and authorization via Bearer JWT tokens

Authentication and authorization are essential for securing microservices architectures. Using Bearer JWT tokens for these processes ensures that only authenticated users with valid tokens can access specific services, protecting sensitive data. Authentication verifies user identity, while authorization controls their permissions. This layered approach not only prevents unauthorized access but also provides detailed control over service interactions, maintaining system security and compliance. Here we leverage Istio mechanisms together with Bearer JWT tokens to fulfill that.

<img src="./docs/OPEA auth flow with OIDC provider.png" width="700" height="300">

### Perform authentication and authorization via fake JWT tokens

Here provides the case of authentication and authorization using fake JWT tokens. Fake JWT tokens are generated through a sample script provided by Istio community.

Expand Down Expand Up @@ -87,7 +96,7 @@ kubectl exec -it -n chatqa $CLIENT_POD -- curl -X POST $accessUrl -d '{"text":"W
kubectl exec -it -n chatqa $CLIENT_POD -- curl -X POST $accessUrl -d '{"text":"What is the revenue of Nike in 2023?","parameters":{"max_new_tokens":17, "do_sample": true}}' -sS -H "Authorization: Bearer $TOKEN2" -H 'Content-Type: application/json' -w " %{http_code}\n"
```

## Perform authentication and authorization via JWT tokens generated by OIDC provider
### Perform authentication and authorization via JWT tokens generated by OIDC provider

An OpenID Connect (OIDC) provider is the service that can help handle authentication and authorization. It provides identity information for users in the form of ID tokens, which contain claims about the user's identity (like their name, email, and user ID). In this example, we choose Keycloak, which is an open source identity and access management project to add authentication to applications and secure services with minimum effort to generate and manage JWT tokens. User can select the OIDC providers including Github, Google, Azure and etc according to their needs.

Expand Down Expand Up @@ -166,3 +175,118 @@ curl -X POST $accessUrl -d '{"text":"What is the revenue of Nike in 2023?","para
# try with the valid token from mary. Shall get the correct response from LLM with http code
curl -X POST $accessUrl -d '{"text":"What is the revenue of Nike in 2023?","parameters":{"max_new_tokens":17, "do_sample": true}}' -sS -H "Authorization: Bearer $TOKENA" -H 'Content-Type: application/json' -w " %{http_code}\n"
```

## Perform authentication and authorization via oauth2-proxy and OIDC provider

Another choice we have is using oauth2-proxy and OIDC providers. These two streamline authentication and authorization by handling user identity and access management. oauth2-Proxy acts as a gateway, integrating with OIDC providers to authenticate users and issue tokens. This setup ensures secure access to applications by validating user credentials and managing permissions, simplifying the implementation of robust security protocols across services.

<img src="./docs/OPEA auth flow with oauth2-proxy.png" width="700" height="400">

We are using a similar scenario here that only privileged users can access our chatQnA service and ask questions. In this case, user `mary` who has the role `user` can access the chatQnA pipeline. And user `bob` with the role `viewer` will not be able to access the service. Of course, the other users without valid token cannot access the service.

**Installation and configuration of oauth2-proxy and OIDC Provider**

Here we take Keycloak as the sample OIDC Provider to use in the example. Follow the steps to install and configure Keycloak.

```bash
cd $(pwd)
kubectl apply -f $(pwd)/config/authN-authZ/keycloak_install.yaml

# get the ip and port to access keycloak.
export HOST_IP=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' | cut -d '/' -f3 | cut -d ':' -f1)
export KEYCLOAK_PORT=$(kubectl get svc keycloak -o jsonpath='{.spec.ports[0].nodePort}')
export KEYCLOAK_ADDR=${HOST_IP}:${KEYCLOAK_PORT}
```
**Note:** Double check if the host ip captured is the correct ip.

Access the Keycloak admin console through the `KEYCLOAK_ADDR` to configure the users as well as roles. Use the username and password specified in the yaml file to login.

The user management is done via Keycloak and the configuration steps look like this:

1. Create a new realm named `chatqna` within Keycloak.

<img src="./docs/create_realm.png" width="600" height="300">

2. Create a new client called `chatqna` and set `Client authentication` to 'On'. Set "http://chatqna-ui.com:${INGRESS_PORT}/*" in the `Valid redirect URIs` part. Note that `INGRESS_PORT` and `INGRESS_HOST` shall be exported following the guide [here](https://istio.io/latest/docs/tasks/traffic-management/ingress/ingress-control/#determining-the-ingress-ip-and-ports). Under the Credentials tab you will now be able to locate `<your client's secret>`, which will be used in the oauth2-proxy configs.

<img src="./docs/create_client_1.png" width="600" height="300">

<img src="./docs/create_client_2.png" width="600" height="300">

<img src="./docs/create_client_3.png" width="600" height="300">

3. Access the dedicated mappers pane by clicking `<your client's id>-dedicated`, located under Assigned client scope to configure a new `Audience` mapper with name `aud-mapper-<your client's id>`. And include Audience in your client with `ID token` and `access token` set to `On`.

<img src="./docs/add_mapper.png" width="600" height="300">

4. Create new roles `user` and `viewer` by navigating to `<your client's id> -> Roles`.

5. Create a new user name as `mary` and another user as `bob` with `Email verified` set to `On`. Set passwords for both users (set 'Temporary' to 'Off').

<img src="./docs/create_user.png" width="600" height="300">

6. Create a new Client Scope with the name `groups` in Keycloak with `Include in Token Scope` set as `On`. Include a mapper of type `Group Membership` and set the `Token Claim Name` to `groups`. If the "Full group path" option is selected, you need to include a "/" separator in the group names defined in the --allowed-group option of OAuth2 Proxy. Example: "/groupname". After creating the Client Scope named `groups` you will need to attach it to your client. Go to Clients and find `<your client's id> -> Client scopes` and add client scope and select `groups` and choose `Optional` and you should now have a client that maps group memberships into the JWT tokens so that Oauth2 Proxy may evaluate them.

<img src="./docs/add_group_scope.png" width="600" height="300">

<img src="./docs/attach_group_scope.png" width="600" height="300">

7. Create two groups `user` and `viewer` by navigating to Groups -> Create group. Assign role `user` to group `user` and role `viewer` to group `viewer` and add user `mary` as a member of group `user` and `bob` as a member of group `viewer`.

Next, we will install the oauth2-proxy and configure the OIDC information.

```bash
# set values for realm name, client name and ingress port
# then deploy oauth2-proxy
export REALM=<YOUR_REALM_NAME>
export CLIENT=<YOUR_CLIENT_NAME>
envsubst < $(pwd)/config/authN-authZ/oauth2_install.yaml | kubectl -n chatqa apply -f -
```

**Expose the pipeline endpoint through Istio Ingressgateway and install chatQnA UI**

Here we expose the chatQnA endpoint through the ingress gateway and then install the chatQnA conversation UI.

```bash
# expose chatqna endpoint
kubectl apply -f $(pwd)/config/authN-authZ/chatQnA_router_gateway_oauth.yaml
# build chatqna UI image
git clone https://github.com/opea-project/GenAIExamples.git
cd GenAIExamples/ChatQnA/docker/ui/
export BACKEND_SERVICE_ENDPOINT="http://${INGRESS_HOST}:${INGRESS_PORT}/"
export DATAPREP_SERVICE_ENDPOINT="http://${INGRESS_HOST}:${INGRESS_PORT}/dataprep"
docker build --no-cache -t opea/chatqna-conversation-ui:latest --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy --build-arg BACKEND_SERVICE_ENDPOINT=$BACKEND_SERVICE_ENDPOINT --build-arg DATAPREP_SERVICE_ENDPOINT=$DATAPREP_SERVICE_ENDPOINT -f ./docker/Dockerfile.react .
# inject image to containerd repo
docker save -o ui.tar opea/chatqna-conversation-ui:latest
sudo ctr -n k8s.io image import ui.tar
# install chatqna conversation UI
cd && cd GenAIInfra/microservices-connector
helm install chatqna-ui ../helm-charts/common/chatqna-ui
# expose ui service outside
kubectl apply -f $(pwd)/config/authN-authZ/chatQnA_ui_gateway.yaml
```

**Add authentication and authorization rules to the pipeline through Istio Ingress Gateway**

Here we apply the authentication and authorization rules.

```bash
# Before applying the authorization rule, need to add the oauth2-proxy as the external authorization provider
kubectl apply -f $(pwd)/config/authN-authZ/chatQnA_istio_external_auth.yaml
# 'envsubst' is used to substitute envs in yaml.
# use 'sudo apt-get install gettext-base' to install envsubst if it does not exist on your machine
# apply the authentication and authorization rule
# these files will restrict user access with valid token (with valid group and role)
envsubst < $(pwd)/config/authN-authZ/chatQnA_authN_oauth.yaml | kubectl -n chatqa apply -f -
envsubst < $(pwd)/config/authN-authZ/chatQnA_authZ_oauth.yaml | kubectl -n chatqa apply -f -
```

**Validate authentication and authorization with UI service**

Add both host names for ChatQnA UI and backend service into /etc/hosts
```bash
sudo sed -i '1i\127.0.0.1 chatqna-service.com' /etc/hosts
sudo sed -i '1i\127.0.0.1 chatqna-ui.com' /etc/hosts
```

Open browser with address "chatqna-ui.com:${INGRESS_PORT}". Login with user `bob` and its credentials shall return a 403 error. Login with user `mary` and its credentials shall able to access the ChatQnA service.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ metadata:
name: jwt-keycloak
spec:
jwtRules:
- issuer: http://${KEYCLOAK_ADDR}/realms/istio
jwksUri: http://${KEYCLOAK_ADDR}/realms/istio/protocol/openid-connect/certs
- issuer: http://${KEYCLOAK_ADDR}/realms/${REALM}
jwksUri: http://${KEYCLOAK_ADDR}/realms/${REALM}/protocol/openid-connect/certs
outputPayloadToHeader: jwt-parsed
selector:
matchLabels:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: oauth2-keycloak-auth
namespace: istio-system
spec:
jwtRules:
- issuer: http://${KEYCLOAK_ADDR}/realms/${REALM}
jwksUri: http://${KEYCLOAK_ADDR}/realms/${REALM}/protocol/openid-connect/certs
selector:
matchLabels:
istio: ingressgateway
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: chatqna-ext-authz
namespace: istio-system
spec:
action: CUSTOM
provider:
name: oauth2-proxy
rules:
- to:
- operation:
notPaths:
- /realms/*
selector:
matchLabels:
istio: ingressgateway
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
apiVersion: v1
data:
mesh: |-
accessLogFile: /dev/stdout
defaultConfig:
discoveryAddress: istiod.istio-system.svc:15012
proxyMetadata: {}
tracing:
zipkin:
address: localhost:9411
enablePrometheusMerge: true
enableTracing: false
outboundTrafficPolicy:
mode: ALLOW_ANY
trustDomain: cluster.local
extensionProviders:
- name: oauth2-proxy
envoyExtAuthzHttp:
service: oauth-proxy.oauth2-proxy.svc.cluster.local
port: 4180
timeout: 1.5s
includeRequestHeadersInCheck: ["authorization", "cookie"]
headersToUpstreamOnAllow: ["x-forwarded-access-token", "authorization", "path", "x-auth-request-user", "x-auth-request-email", "x-auth-request-access-token", "x-auth-request-groups"]
headersToDownstreamOnDeny: ["content-type", "set-cookie"]
meshNetworks: 'networks: {}'
kind: ConfigMap
metadata:
labels:
install.operator.istio.io/owning-resource: installed-state
install.operator.istio.io/owning-resource-namespace: istio-system
istio.io/rev: default
operator.istio.io/component: Pilot
operator.istio.io/managed: Reconcile
operator.istio.io/version: 1.22.2
release: istio
name: istio
namespace: istio-system
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

---
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: chatqna-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- chatqna-service.com
port:
name: http
number: 80
protocol: HTTP
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: chatqna-virtual-service
namespace: istio-system
spec:
gateways:
- istio-system/chatqna-gateway
hosts:
- chatqna-service.com
http:
- match:
- uri:
prefix: /
route:
- destination:
host: router-service.chatqa.svc.cluster.local
port:
number: 8080
47 changes: 47 additions & 0 deletions microservices-connector/config/authN-authZ/chatQnA_ui_gateway.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

---
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: chatqna-ui-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- chatqna-ui.com
port:
name: http
number: 80
protocol: HTTP
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: chatqna-ui-virtual-service
namespace: istio-system
spec:
gateways:
- istio-system/chatqna-ui-gateway
hosts:
- chatqna-ui.com
http:
- match:
- uri:
prefix: /oauth2
route:
- destination:
host: oauth-proxy.oauth2-proxy.svc.cluster.local
port:
number: 4180
- match:
- uri:
prefix: /
route:
- destination:
host: ui.default.svc.cluster.local
port:
number: 5174
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 2b12a68

Please sign in to comment.