feat: updated backend for identity auth with reviewer optional

This commit is contained in:
=
2025-03-22 01:06:58 +05:30
parent 351e573fea
commit 81b026865c
6 changed files with 36 additions and 10 deletions
@@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasReviewerJwtCol = await knex.schema.hasColumn(
TableName.IdentityKubernetesAuth,
"encryptedKubernetesTokenReviewerJwt"
);
if (hasReviewerJwtCol) {
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => {
t.binary("encryptedKubernetesTokenReviewerJwt").nullable().alter();
});
}
}
export async function down(): Promise<void> {
// we can't make it back to non nullable, it will fail
}
@@ -28,7 +28,7 @@ export const IdentityKubernetesAuthsSchema = z.object({
allowedNamespaces: z.string(), allowedNamespaces: z.string(),
allowedNames: z.string(), allowedNames: z.string(),
allowedAudience: z.string(), allowedAudience: z.string(),
encryptedKubernetesTokenReviewerJwt: zodBuffer, encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(),
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional() encryptedKubernetesCaCertificate: zodBuffer.nullable().optional()
}); });
+2 -2
View File
@@ -244,7 +244,7 @@ export const KUBERNETES_AUTH = {
kubernetesHost: "The host string, host:port pair, or URL to the base of the Kubernetes API server.", kubernetesHost: "The host string, host:port pair, or URL to the base of the Kubernetes API server.",
caCert: "The PEM-encoded CA cert for the Kubernetes API server.", caCert: "The PEM-encoded CA cert for the Kubernetes API server.",
tokenReviewerJwt: tokenReviewerJwt:
"The long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods.", "Optional JWT token for accessing Kubernetes TokenReview API. If provided, this long-lived token will be used to validate service account tokens during authentication. If omitted, the client's own JWT will be used instead, which requires the client to have the system:auth-delegator ClusterRole binding.",
allowedNamespaces: allowedNamespaces:
"The comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.", "The comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.",
allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.", allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.",
@@ -260,7 +260,7 @@ export const KUBERNETES_AUTH = {
kubernetesHost: "The new host string, host:port pair, or URL to the base of the Kubernetes API server.", kubernetesHost: "The new host string, host:port pair, or URL to the base of the Kubernetes API server.",
caCert: "The new PEM-encoded CA cert for the Kubernetes API server.", caCert: "The new PEM-encoded CA cert for the Kubernetes API server.",
tokenReviewerJwt: tokenReviewerJwt:
"The new long-lived service account JWT token for Infisical to access the TokenReview API to validate other service account JWT tokens submitted by applications/pods.", "Optional JWT token for accessing Kubernetes TokenReview API. If provided, this long-lived token will be used to validate service account tokens during authentication. If omitted, the client's own JWT will be used instead, which requires the client to have the system:auth-delegator ClusterRole binding.",
allowedNamespaces: allowedNamespaces:
"The new comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.", "The new comma-separated list of trusted namespaces that service accounts must belong to authenticate with Infisical.",
allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.", allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.",
@@ -24,7 +24,7 @@ const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick(
allowedAudience: true allowedAudience: true
}).extend({ }).extend({
caCert: z.string(), caCert: z.string(),
tokenReviewerJwt: z.string() tokenReviewerJwt: z.string().optional().nullable()
}); });
export const registerIdentityKubernetesRouter = async (server: FastifyZodProvider) => { export const registerIdentityKubernetesRouter = async (server: FastifyZodProvider) => {
@@ -98,7 +98,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
.object({ .object({
kubernetesHost: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.kubernetesHost), kubernetesHost: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.kubernetesHost),
caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert), caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert),
tokenReviewerJwt: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt), tokenReviewerJwt: z.string().trim().optional().describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt),
allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation
allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames), allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames),
allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience), allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience),
@@ -195,7 +195,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
.object({ .object({
kubernetesHost: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.kubernetesHost), kubernetesHost: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.kubernetesHost),
caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert), caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert),
tokenReviewerJwt: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt), tokenReviewerJwt: z.string().trim().nullable().optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt),
allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation
allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames), allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames),
allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience), allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience),
@@ -84,6 +84,9 @@ export const identityKubernetesAuthServiceFactory = ({
tokenReviewerJwt = decryptor({ tokenReviewerJwt = decryptor({
cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt
}).toString(); }).toString();
} else {
// if no token reviewer is provided means the incoming token has to act as reviewer
tokenReviewerJwt = serviceAccountJwt;
} }
const { data } = await axios const { data } = await axios
@@ -291,7 +294,9 @@ export const identityKubernetesAuthServiceFactory = ({
accessTokenTTL, accessTokenTTL,
accessTokenNumUsesLimit, accessTokenNumUsesLimit,
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps), accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
encryptedKubernetesTokenReviewerJwt: encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob, encryptedKubernetesTokenReviewerJwt: tokenReviewerJwt
? encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob
: null,
encryptedKubernetesCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob encryptedKubernetesCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob
}, },
tx tx
@@ -387,10 +392,12 @@ export const identityKubernetesAuthServiceFactory = ({
updateQuery.encryptedKubernetesCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; updateQuery.encryptedKubernetesCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob;
} }
if (tokenReviewerJwt !== undefined) { if (tokenReviewerJwt) {
updateQuery.encryptedKubernetesTokenReviewerJwt = encryptor({ updateQuery.encryptedKubernetesTokenReviewerJwt = encryptor({
plainText: Buffer.from(tokenReviewerJwt) plainText: Buffer.from(tokenReviewerJwt)
}).cipherTextBlob; }).cipherTextBlob;
} else if (tokenReviewerJwt === null) {
updateQuery.encryptedKubernetesTokenReviewerJwt = null;
} }
const updatedKubernetesAuth = await identityKubernetesAuthDAL.updateById(identityKubernetesAuth.id, updateQuery); const updatedKubernetesAuth = await identityKubernetesAuthDAL.updateById(identityKubernetesAuth.id, updateQuery);
@@ -9,7 +9,7 @@ export type TAttachKubernetesAuthDTO = {
identityId: string; identityId: string;
kubernetesHost: string; kubernetesHost: string;
caCert: string; caCert: string;
tokenReviewerJwt: string; tokenReviewerJwt?: string;
allowedNamespaces: string; allowedNamespaces: string;
allowedNames: string; allowedNames: string;
allowedAudience: string; allowedAudience: string;
@@ -24,7 +24,7 @@ export type TUpdateKubernetesAuthDTO = {
identityId: string; identityId: string;
kubernetesHost?: string; kubernetesHost?: string;
caCert?: string; caCert?: string;
tokenReviewerJwt?: string; tokenReviewerJwt?: string | null;
allowedNamespaces?: string; allowedNamespaces?: string;
allowedNames?: string; allowedNames?: string;
allowedAudience?: string; allowedAudience?: string;