Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

keycloak auth initial implementation #371

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ stop-dev-kind: check-kind ## Stop the Kind cluster to destroy the development en

.PHONY: setup-kind
setup-kind: check-kind check-kubectl stop-dev-kind ## Create a Kind cluster with ingress enabled
$(CMD_PREFIX) kind create cluster --config ./deploy/k8s/overlays/kind/kind.yaml
$(CMD_PREFIX) kind create cluster --config ./deploy/k8s/overlays/kind/ui/kind.yaml
$(CMD_PREFIX) kubectl cluster-info
$(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) apply -f ./deploy/k8s/overlays/kind/kind-ingress.yaml
$(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) apply -f ./deploy/k8s/overlays/kind/ui/kind-ingress.yaml

.PHONY: wait-for-readiness
wait-for-readiness: # Wait for operators to be ready
Expand All @@ -136,8 +136,8 @@ deploy: wait-for-readiness ## Deploy a InstructLab UI development stack onto a k
echo "Please create a .env file in the root of the project." ; \
exit 1 ; \
fi
$(CMD_PREFIX) yes | cp -rf .env ./deploy/k8s/overlays/kind/.env
$(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) apply -k ./deploy/k8s/overlays/kind
$(CMD_PREFIX) yes | cp -rf .env ./deploy/k8s/overlays/kind/ui/.env
$(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) apply -k ./deploy/k8s/overlays/kind/ui
$(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) wait --for=condition=Ready pods -n $(ILAB_KUBE_NAMESPACE) --all -l app.kubernetes.io/part-of=ui --timeout=15m

.PHONY: redeploy
Expand All @@ -147,8 +147,8 @@ redeploy: ui-image load-images ## Redeploy the InstructLab UI stack onto a kuber

.PHONY: undeploy
undeploy: ## Undeploy the InstructLab UI stack from a kubernetes cluster
$(CMD_PREFIX) if [ -f ./deploy/k8s/overlays/kind/.env ]; then \
rm ./deploy/k8s/overlays/kind/.env ; \
$(CMD_PREFIX) if [ -f ./deploy/k8s/overlays/kind/ui/.env ]; then \
rm ./deploy/k8s/overlays/kind/ui/.env ; \
fi
$(CMD_PREFIX) kubectl --context=$(ILAB_KUBE_CONTEXT) delete namespace $(ILAB_KUBE_NAMESPACE)

Expand Down
56 changes: 56 additions & 0 deletions deploy/k8s/base/keycloak/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Deployment guide

## Keycloak setup

These are stock out of the box rhsso + keycloak deployments that will work with the instructlab-ui as its OIDC provider.
To apply these manifests, first apply the `operator`, wait for the rhsso operator to come online, and then proceed to
apply the `resources`.

```bash

oc --kustomize apply operator/
### wait for keycloak, keycloak-psql and rhsso pods to come up

oc get pods -n keycloak-system
# expect:
# NAME READY STATUS RESTARTS AGE
# keycloak-0 1/1 Running 0 32h
# keycloak-postgresql-5965b4dd55-22hsc 1/1 Running 0 11d
# rhsso-operator-758844f657-s97k9 1/1 Running 0 32h

cd resources/

oc create -f keycloak.yaml
# wait for keycloak instance to be initialized and in the `reconciling` stage with status `ready`,
# this may take up to a few minutes

oc create -f realm.yaml
# wait for the keycloakRealm to be `reconciling`

oc create -f instructlab-ui-client.yaml && oc create -f user.yaml
# These two are very fast, wait for them to also be in the `reconciling` stage

# In total, this will create the Keycloak instance, KeycloakClient, KeycloakRealm, and a dummy user
```

## Application setup

Now, we need to setup our `.env` file so that we can use the resources we just created to authenticate.
We need to set the following values: `KEYCLOAK_CLIENT_ID`, `KEYCLOAK_CLIENT_SECRET`, and `KEYCLOAK_ISSUER`.
To get these values you can do the following (this assumes no modification to the manifests):

```bash
export KEYCLOAK_CLIENT_ID=$(kubectl get secret keycloak-client-secret-instructlab-ui -n keycloak-system -o yaml | yq .data.CLIENT_ID | base64 -d)
export KEYCLOAK_CLIENT_SECRET=$(kubectl get secret keycloak-client-secret-instructlab-ui -n keycloak-system -o yaml | yq .data.CLIENT_SECRET | base64 -d)
export KEYCLOAK_ISSUER=$(kubectl get keycloak keycloak -n keycloak-system -o jsonpath='{.status.externalURL}')/auth/realms/instructlab-ui

echo "KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID}" >> .env
echo "KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET}" >> .env
echo "KEYCLOAK_ISSUER=${KEYCLOAK_ISSUER}" >> .env

cat .env | tail -n 3
```

Additionally, make sure the `IL_UI_DEPLOYMENT=dev` value is set in the `.env` file, because keycloak auth is not currently available
in the production deployment. After this you can build and run the application however you like. When you go to sign in, make sure
to use the credentials you specificed in the [user manifest](/resources/user.yaml), credentials to the admin console will not work.
8 changes: 8 additions & 0 deletions deploy/k8s/base/keycloak/operator/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- namespace.yaml
- subscription.yaml

namespace: keycloak-system
4 changes: 4 additions & 0 deletions deploy/k8s/base/keycloak/operator/namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: keycloak-system
20 changes: 20 additions & 0 deletions deploy/k8s/base/keycloak/operator/subscription.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: rhsso-operator
spec:
channel: stable
installPlanApproval: Automatic
name: rhsso-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: keycloak-system-instructlab-ui
spec:
targetNamespaces:
- keycloak-system
upgradeStrategy: Default
54 changes: 54 additions & 0 deletions deploy/k8s/base/keycloak/resources/instructlab-ui-client.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
apiVersion: keycloak.org/v1alpha1
kind: KeycloakClient
metadata:
labels:
app: sso
name: instructlab-ui
namespace: keycloak-system
spec:
client:
clientAuthenticatorType: client-secret
clientId: instructlab-ui
defaultClientScopes:
- profile
- email
description: Client for Instructlab-UI authentication
directAccessGrantsEnabled: true
implicitFlowEnabled: false
name: instructlab-ui
protocol: openid-connect
protocolMappers:
- config:
claim.name: email
id.token.claim: "true"
jsonType.label: String
user.attribute: email
userinfo.token.claim: "true"
name: email
protocol: openid-connect
protocolMapper: oidc-usermodel-property-mapper
- config:
claim.name: email-verified
id.token.claim: "true"
user.attribute: emailVerified
userinfo.token.claim: "true"
name: email-verified
protocol: openid-connect
protocolMapper: oidc-usermodel-property-mapper
- config:
access.token.claim: "true"
claim.name: aud
claim.value: instructlab-ui
id.token.claim: "true"
userinfo.token.claim: "true"
name: audience
protocol: openid-connect
protocolMapper: oidc-hardcoded-claim-mapper
publicClient: false
redirectUris:
- '*'
standardFlowEnabled: true
realmSelector:
matchLabels:
app: sso
scopeMappings: {}
15 changes: 15 additions & 0 deletions deploy/k8s/base/keycloak/resources/keycloak.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: keycloak.org/v1alpha1
kind: Keycloak
metadata:
labels:
app: sso
name: keycloak
namespace: keycloak-system
spec:
externalAccess:
enabled: true
instances: 1
keycloakDeploymentSpec:
imagePullPolicy: Always
postgresDeploymentSpec:
imagePullPolicy: Always
7 changes: 7 additions & 0 deletions deploy/k8s/base/keycloak/resources/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- keycloak.yaml
- realm.yaml
- instructlab-ui-client.yaml
- user.yaml
17 changes: 17 additions & 0 deletions deploy/k8s/base/keycloak/resources/realm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: keycloak.org/v1alpha1
kind: KeycloakRealm
metadata:
labels:
app: sso
name: instructlab-ui
namespace: keycloak-system
spec:
instanceSelector:
matchLabels:
app: sso
realm:
displayName: Instructlab-UI
enabled: true
id: instructlab-ui
realm: instructlab-ui
sslRequired: none
22 changes: 22 additions & 0 deletions deploy/k8s/base/keycloak/resources/user.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
apiVersion: keycloak.org/v1alpha1
kind: KeycloakUser
metadata:
labels:
app: sso
name: dummy
namespace: keycloak-system
spec:
realmSelector:
matchLabels:
app: sso
user:
email: [email protected]
enabled: true
emailVerified: true
credentials:
- type: "password"
value: "dummy"
firstName: dummy
lastName: dummy
username: dummy
10 changes: 10 additions & 0 deletions deploy/k8s/overlays/kind/keycloak/operator/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../../../base/keycloak/operator/

patches:
- path: patch.yaml


9 changes: 9 additions & 0 deletions deploy/k8s/overlays/kind/keycloak/operator/patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: rhsso-operator
spec:
channel: alpha
name: keycloak-operator
source: operatorhubio-catalog
sourceNamespace: olm
12 changes: 12 additions & 0 deletions deploy/k8s/overlays/kind/keycloak/resources/keycloak-svc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: keycloak-internal
spec:
selector:
app: keycloak
component: keycloak
ports:
- protocol: TCP
port: 80
targetPort: 8080
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: keycloak.org/v1alpha1
kind: Keycloak
metadata:
name: keycloak
spec:
externalAccess:
enabled: false

12 changes: 12 additions & 0 deletions deploy/k8s/overlays/kind/keycloak/resources/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: keycloak-system

resources:
- ../../../../base/keycloak/resources/
- keycloak-svc.yaml

patches:
- path: keycloak_patch.yaml
- path: realm_patch.yaml
7 changes: 7 additions & 0 deletions deploy/k8s/overlays/kind/keycloak/resources/realm_patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: keycloak.org/v1alpha1
kind: KeycloakRealm
metadata:
name: instructlab-ui
spec:
realm:
sslRequired: none
1 change: 1 addition & 0 deletions public/keycloak-icon-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 14 additions & 2 deletions src/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import NextAuth, { NextAuthOptions } from 'next-auth';
import GitHubProvider from 'next-auth/providers/github';
import CredentialsProvider from 'next-auth/providers/credentials';
import KeycloakProvider from 'next-auth/providers/keycloak';
import axios from 'axios';
import winston from 'winston';
import path from 'path';

// Extend the Session and JWT types
declare module 'next-auth' {
interface Session {
provider?: string;
accessToken?: string;
id?: string;
}
Expand All @@ -20,6 +22,7 @@ declare module 'next-auth' {

declare module 'next-auth/jwt' {
interface JWT {
provider?: string;
accessToken?: string;
id?: string;
}
Expand All @@ -42,10 +45,17 @@ const ORG = process.env.NEXT_PUBLIC_AUTHENTICATION_ORG!;
const authOptions: NextAuthOptions = {
providers: [
GitHubProvider({
name: 'Github',
clientId: process.env.OAUTH_GITHUB_ID!,
clientSecret: process.env.OAUTH_GITHUB_SECRET!,
authorization: { params: { scope: 'public_repo' } }
}),
KeycloakProvider({
name: 'Keycloak',
clientId: process.env.KEYCLOAK_CLIENT_ID!,
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!,
issuer: process.env.KEYCLOAK_ISSUER
}),
CredentialsProvider({
name: 'Credentials',
credentials: {
Expand Down Expand Up @@ -73,20 +83,22 @@ const authOptions: NextAuthOptions = {
async jwt({ token, user, account }) {
if (account) {
token.accessToken = account.access_token!;
token.provider = account.provider!;
}
if (user) {
token.id = user.id;
}
// Uncomment for JWT debugging
// // Uncomment for JWT debugging
// console.log('JWT Callback:', token);
return token;
},
async session({ session, token }) {
if (token) {
session.accessToken = token.accessToken;
session.id = token.id;
session.provider = token.provider;
}
// Uncomment for session callback debugging
// // Uncomment for session callback debugging
// console.log('Session Callback:', session);
return session;
},
Expand Down
17 changes: 17 additions & 0 deletions src/app/login/keycloakicon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Pulled from: https://github.com/keycloak/keycloak-misc/blob/main/logo/icon-white.svg
const KeycloakIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 45 35">
<path
d="m117.344 128.588-23.017.003-5.716 9.865-5.796 10.065 5.737 9.962 5.782 9.965 23.011-.008 5.69-9.956h3.976v-.004h.02l-.007-19.93h-3.995zm-19.188 6.65h3.835l-.001.002 1.917 3.315-5.75 9.973 5.743 9.954-1.909 3.332.002.003h-3.837l-5.754-9.966-1.925-3.307 1.952-3.386zm11.499.002 3.842.003 5.754 9.962v.003l1.921 3.341-7.677 13.266h-3.83v-.001c-.706-1.036-1.914-3.33-1.914-3.33l5.753-9.958-5.754-9.973z"
style={{
display: 'inline',
fill: '#fff',
fillOpacity: 1,
strokeWidth: '.264583'
}}
transform="translate(-82.815 -128.588)"
/>
</svg>
);

export default KeycloakIcon;
Loading