Skip to content
This repository has been archived by the owner on Apr 5, 2024. It is now read-only.

feat: add keycloak secrets #231

Open
wants to merge 1 commit into
base: hasura
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
15 changes: 15 additions & 0 deletions .kontinuous/patches/service-account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
Patch manifests
*/
module.exports = (manifests) => {
for (const manifest of manifests) {
const { kind } = manifest;
if (kind === "Deployment" && manifest.metadata.name === "app") {
manifest.spec.template.spec = {
...manifest.spec.template.spec,
serviceAccountName: "vault"
};
}
}
return manifests;
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"next-auth": "^4.22.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"tss-react": "^4.8.8"
"tss-react": "^4.8.8",
"node-vault": "^0.10.2"
},
"devDependencies": {
"@babel/core": "^7.22.9",
Expand Down
149 changes: 82 additions & 67 deletions src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { NextApiRequest, NextApiResponse } from "next"
import type { NextRequest } from "next/server"
import NextAuth, { Session } from "next-auth";
import { Account } from "next-auth";
import { User } from "next-auth";
import { JWT } from "next-auth/jwt";
import KeycloakProvider from "next-auth/providers/keycloak";
import VaultModule from "../../../../vault/VaultModule"

import { refreshAccessToken } from "../../../lib/auth";

Expand All @@ -14,75 +17,87 @@ interface ExtendedToken extends JWT {
user: User;
}

export default NextAuth({
debug: true,
providers: [
KeycloakProvider({
clientId: process.env.KEYCLOAK_CLIENT_ID ?? "",
clientSecret: process.env.KEYCLOAK_CLIENT_SECRET ?? "",
issuer: process.env.KEYCLOAK_URL ?? "",
}),
],
export default async function auth(req: any, res: any) {

callbacks: {
//@ts-ignore
async jwt({
token,
user,
account,
}: {
token: JWT;
user: User;
account: Account;
}) {
// Initial sign in
//console.log("jwt", { token, user, account });
if (account && user) {
return {
accessToken: account.access_token,
refreshToken: account.refresh_token,
idToken: account.id_token,
accessTokenExpires: account.expires_at
? account.expires_at * 1000
: null,
user,
};
}
console.log("auth method triggered !")
const vaultModule = new VaultModule("integrated");
const keycloakClientId = await vaultModule.readSecret("kv/data/integrated/keycloak_client_id")
const keycloakClientSecret = await vaultModule.readSecret("kv/data/integrated/keycloak_client_secret")
const nextauthSecret = await vaultModule.readSecret("kv/data/integrated/nextauth_secret")

// Return previous token if the access token has not expired yet
if (
token.accessTokenExpires &&
Date.now() < Number(token.accessTokenExpires)
) {
return token as ExtendedToken;
}
console.log("Secret client id: ", keycloakClientId)

// Access token has expired, try to update it
return refreshAccessToken(token);
},
// @ts-ignore
async session({
session,
token,
}: {
session: Session;
token: ExtendedToken;
}) {
if (token) {
session.user = token.user;
//@ts-ignore
session.accessToken = token.accessToken;
//@ts-ignore
session.refreshToken = token.refreshToken;
//@ts-ignore
session.idToken = token.idToken;
//@ts-ignore
session.accessTokenExpires = token.accessTokenExpires;
//@ts-ignore
session.error = token.error;
}
// @ts-ignore
return NextAuth(req, res, {
debug: true,
providers: [
KeycloakProvider({
clientId: keycloakClientId ?? "",
clientSecret: keycloakClientSecret ?? "",
issuer: process.env.KEYCLOAK_URL ?? "",
}),
],
secret: nextauthSecret,
callbacks: {
//@ts-ignore
async jwt({
token,
user,
account,
}: {
token: JWT;
user: User;
account: Account;
}) {
// Initial sign in
//console.log("jwt", { token, user, account });
if (account && user) {
return {
accessToken: account.access_token,
refreshToken: account.refresh_token,
idToken: account.id_token,
accessTokenExpires: account.expires_at
? account.expires_at * 1000
: null,
user,
};
}

// Return previous token if the access token has not expired yet
if (
token.accessTokenExpires &&
Date.now() < Number(token.accessTokenExpires)
) {
return token as ExtendedToken;
}

return session;
// Access token has expired, try to update it
return refreshAccessToken(token);
},
// @ts-ignore
async session({
session,
token,
}: {
session: Session;
token: ExtendedToken;
}) {
if (token) {
session.user = token.user;
//@ts-ignore
session.accessToken = token.accessToken;
//@ts-ignore
session.refreshToken = token.refreshToken;
//@ts-ignore
session.idToken = token.idToken;
//@ts-ignore
session.accessTokenExpires = token.accessTokenExpires;
//@ts-ignore
session.error = token.error;
}

return session;
},
},
},
});
})
}
48 changes: 48 additions & 0 deletions vault/VaultModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as fs from 'fs';

const vault = require("node-vault");


class VaultModule {
private vaultClient: any;
private readonly vaultRole: string
private isKubelogged: boolean

constructor(vaultRole: string) {
this.vaultClient = vault({
apiVersion: 'v1',
endpoint: "http://vault.vault-dev.svc:8200",
});
this.vaultRole = vaultRole
this.isKubelogged = false
}

async readSecret(path: string): Promise<any> {
const JWT_TOKEN_FILE="/var/run/secrets/kubernetes.io/serviceaccount/token";
const jwt = fs.readFileSync(JWT_TOKEN_FILE);

if (!this.isKubelogged) {
try {
const result = await this.vaultClient.kubernetesLogin({
"role": this.vaultRole,
"jwt": jwt.toString()
});
this.vaultClient.token = result.auth.client_token;
} catch (error) {
console.error('Error authenticating to vault instance:', error);
throw error;
}
this.isKubelogged = true
}
try {
const res = await this.vaultClient.read(path);
let obj = Object.keys(res.data.data)
return res.data.data[obj[0]]
} catch (error) {
console.error('Error reading secret:', error);
throw error;
}
}
}

export default VaultModule;
19 changes: 19 additions & 0 deletions vault/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "vault-integrated",
"version": "1.0.0",
"description": "Vault integrated for template application",
"main": "app.js",
"scripts": {
"build": "tsc",
"start": "node index.ts"
},
"dependencies": {
"node-vault": "^0.10.2"
},
"devDependencies": {
"typescript": "5.1.6"
},
"engines": {
"node": "18.11.17"
}
}
16 changes: 16 additions & 0 deletions vault/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"outDir": ".",
"rootDir": ".",
"strict": true,
"esModuleInterop": true
},
"include": [
"*.ts"
],
"exclude": [
"node_modules"
]
}
Loading
Loading