diff --git a/backend/src/db/migrations/20250321100157_k8s-self-reviewer-jwt.ts b/backend/src/db/migrations/20250321100157_k8s-self-reviewer-jwt.ts new file mode 100644 index 000000000..6cc3a2696 --- /dev/null +++ b/backend/src/db/migrations/20250321100157_k8s-self-reviewer-jwt.ts @@ -0,0 +1,19 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + 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 { + // we can't make it back to non nullable, it will fail +} diff --git a/backend/src/db/schemas/identity-kubernetes-auths.ts b/backend/src/db/schemas/identity-kubernetes-auths.ts index 85f210ff1..448cec386 100644 --- a/backend/src/db/schemas/identity-kubernetes-auths.ts +++ b/backend/src/db/schemas/identity-kubernetes-auths.ts @@ -28,7 +28,7 @@ export const IdentityKubernetesAuthsSchema = z.object({ allowedNamespaces: z.string(), allowedNames: z.string(), allowedAudience: z.string(), - encryptedKubernetesTokenReviewerJwt: zodBuffer, + encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(), encryptedKubernetesCaCertificate: zodBuffer.nullable().optional() }); diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 8338492a4..d48059e1f 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -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.", caCert: "The PEM-encoded CA cert for the Kubernetes API server.", 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: "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.", @@ -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.", caCert: "The new PEM-encoded CA cert for the Kubernetes API server.", 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: "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.", diff --git a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts index 952cfcdaf..0105afd76 100644 --- a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts +++ b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts @@ -24,7 +24,7 @@ const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick( allowedAudience: true }).extend({ caCert: z.string(), - tokenReviewerJwt: z.string() + tokenReviewerJwt: z.string().optional().nullable() }); export const registerIdentityKubernetesRouter = async (server: FastifyZodProvider) => { @@ -98,7 +98,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide .object({ kubernetesHost: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.kubernetesHost), 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 allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames), allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience), @@ -195,7 +195,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide .object({ kubernetesHost: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.kubernetesHost), 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 allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames), allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience), diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index 8e4e46bc7..e68e4e309 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -84,6 +84,9 @@ export const identityKubernetesAuthServiceFactory = ({ tokenReviewerJwt = decryptor({ cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt }).toString(); + } else { + // if no token reviewer is provided means the incoming token has to act as reviewer + tokenReviewerJwt = serviceAccountJwt; } const { data } = await axios @@ -291,7 +294,9 @@ export const identityKubernetesAuthServiceFactory = ({ accessTokenTTL, accessTokenNumUsesLimit, 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 }, tx @@ -387,10 +392,12 @@ export const identityKubernetesAuthServiceFactory = ({ updateQuery.encryptedKubernetesCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; } - if (tokenReviewerJwt !== undefined) { + if (tokenReviewerJwt) { updateQuery.encryptedKubernetesTokenReviewerJwt = encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob; + } else if (tokenReviewerJwt === null) { + updateQuery.encryptedKubernetesTokenReviewerJwt = null; } const updatedKubernetesAuth = await identityKubernetesAuthDAL.updateById(identityKubernetesAuth.id, updateQuery); diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-types.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-types.ts index c66ec8480..b3bbcb49e 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-types.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-types.ts @@ -9,7 +9,7 @@ export type TAttachKubernetesAuthDTO = { identityId: string; kubernetesHost: string; caCert: string; - tokenReviewerJwt: string; + tokenReviewerJwt?: string; allowedNamespaces: string; allowedNames: string; allowedAudience: string; @@ -24,7 +24,7 @@ export type TUpdateKubernetesAuthDTO = { identityId: string; kubernetesHost?: string; caCert?: string; - tokenReviewerJwt?: string; + tokenReviewerJwt?: string | null; allowedNamespaces?: string; allowedNames?: string; allowedAudience?: string;