Merge pull request #3731 from Infisical/daniel/gateway-auth-methods

feat(identities/kubernetes-auth): gateway as token reviewer
This commit is contained in:
Maidul Islam
2025-06-05 16:33:24 -04:00
committed by GitHub
29 changed files with 1374 additions and 662 deletions
@@ -843,7 +843,8 @@ export const useAddIdentityKubernetesAuth = () => {
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
gatewayId
gatewayId,
tokenReviewMode
}) => {
const {
data: { identityKubernetesAuth }
@@ -860,7 +861,8 @@ export const useAddIdentityKubernetesAuth = () => {
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
gatewayId
gatewayId,
tokenReviewMode
}
);
@@ -950,7 +952,8 @@ export const useUpdateIdentityKubernetesAuth = () => {
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
gatewayId
gatewayId,
tokenReviewMode
}) => {
const {
data: { identityKubernetesAuth }
@@ -967,7 +970,8 @@ export const useUpdateIdentityKubernetesAuth = () => {
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
gatewayId
gatewayId,
tokenReviewMode
}
);
@@ -379,10 +379,16 @@ export type DeleteIdentityAzureAuthDTO = {
identityId: string;
};
export enum IdentityKubernetesAuthTokenReviewMode {
Api = "api",
Gateway = "gateway"
}
export type IdentityKubernetesAuth = {
identityId: string;
kubernetesHost: string;
tokenReviewerJwt: string;
tokenReviewMode: IdentityKubernetesAuthTokenReviewMode;
allowedNamespaces: string;
allowedNames: string;
allowedAudience: string;
@@ -399,6 +405,7 @@ export type AddIdentityKubernetesAuthDTO = {
identityId: string;
kubernetesHost: string;
tokenReviewerJwt?: string;
tokenReviewMode: IdentityKubernetesAuthTokenReviewMode;
allowedNamespaces: string;
allowedNames: string;
allowedAudience: string;
@@ -417,6 +424,7 @@ export type UpdateIdentityKubernetesAuthDTO = {
identityId: string;
kubernetesHost?: string;
tokenReviewerJwt?: string | null;
tokenReviewMode?: IdentityKubernetesAuthTokenReviewMode;
allowedNamespaces?: string;
allowedNames?: string;
allowedAudience?: string;
@@ -33,13 +33,19 @@ import {
useGetIdentityKubernetesAuth,
useUpdateIdentityKubernetesAuth
} from "@app/hooks/api";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import {
IdentityKubernetesAuthTokenReviewMode,
IdentityTrustedIp
} from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const schema = z
.object({
tokenReviewMode: z
.nativeEnum(IdentityKubernetesAuthTokenReviewMode)
.default(IdentityKubernetesAuthTokenReviewMode.Api),
kubernetesHost: z.string().min(1),
tokenReviewerJwt: z.string().optional(),
gatewayId: z.string().optional().nullable(),
@@ -62,7 +68,15 @@ const schema = z
)
.min(1)
})
.required();
.superRefine((data, ctx) => {
if (data.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Gateway && !data.gatewayId) {
ctx.addIssue({
path: ["gatewayId"],
code: z.ZodIssueCode.custom,
message: "When token review mode is set to Gateway, a gateway must be selected"
});
}
});
export type FormData = z.infer<typeof schema>;
@@ -100,11 +114,14 @@ export const IdentityKubernetesAuthForm = ({
control,
handleSubmit,
reset,
watch,
setValue,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
tokenReviewMode: IdentityKubernetesAuthTokenReviewMode.Api,
kubernetesHost: "",
tokenReviewerJwt: "",
allowedNames: "",
@@ -128,6 +145,7 @@ export const IdentityKubernetesAuthForm = ({
useEffect(() => {
if (data) {
reset({
tokenReviewMode: data.tokenReviewMode,
kubernetesHost: data.kubernetesHost,
tokenReviewerJwt: data.tokenReviewerJwt,
allowedNames: data.allowedNames,
@@ -148,6 +166,7 @@ export const IdentityKubernetesAuthForm = ({
});
} else {
reset({
tokenReviewMode: IdentityKubernetesAuthTokenReviewMode.Api,
kubernetesHost: "",
tokenReviewerJwt: "",
allowedNames: "",
@@ -173,6 +192,7 @@ export const IdentityKubernetesAuthForm = ({
accessTokenMaxTTL,
accessTokenNumUsesLimit,
gatewayId,
tokenReviewMode,
accessTokenTrustedIps
}: FormData) => {
try {
@@ -189,6 +209,7 @@ export const IdentityKubernetesAuthForm = ({
caCert,
identityId,
gatewayId: gatewayId || null,
tokenReviewMode,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@@ -205,6 +226,7 @@ export const IdentityKubernetesAuthForm = ({
allowedAudience: allowedAudience || "",
gatewayId: gatewayId || null,
caCert: caCert || "",
tokenReviewMode,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
@@ -228,6 +250,8 @@ export const IdentityKubernetesAuthForm = ({
}
};
const tokenReviewMode = watch("tokenReviewMode");
return (
<form
onSubmit={handleSubmit(onFormSubmit, (fields) => {
@@ -235,6 +259,7 @@ export const IdentityKubernetesAuthForm = ({
[
"kubernetesHost",
"tokenReviewerJwt",
"tokenReviewMode",
"gatewayId",
"accessTokenTTL",
"accessTokenMaxTTL",
@@ -269,21 +294,113 @@ export const IdentityKubernetesAuthForm = ({
</FormControl>
)}
/>
<Controller
control={control}
name="tokenReviewerJwt"
render={({ field, fieldState: { error } }) => (
<FormControl
tooltipClassName="max-w-md"
label="Token Reviewer JWT"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="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."
<div className="flex w-full items-center gap-2">
<div className="w-full flex-1">
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
<Input {...field} placeholder="" type="password" />
</FormControl>
)}
/>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
isOptional
>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={(v) => {
if (v !== "") {
onChange(v);
}
if (v === null) {
setValue(
"tokenReviewMode",
IdentityKubernetesAuthTokenReviewMode.Api,
{
shouldDirty: true,
shouldTouch: true
}
);
}
}}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewayLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => {
onChange(null);
}}
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
</div>
<Controller
control={control}
name="tokenReviewMode"
render={({ field }) => (
<FormControl
tooltipClassName="max-w-md"
tooltipText="The method of which tokens are reviewed. If you select Gateway as Reviewer, the selected gateway will be used to review tokens with. If this option is enabled, the gateway must be deployed in Kubernetes, and the gateway must have the system:auth-delegator ClusterRole binding."
label="Review Mode"
>
<Select value={field.value} onValueChange={field.onChange}>
<SelectItem value="gateway">Gateway as Reviewer</SelectItem>
<SelectItem value="api">Manual Token Reviewer JWT (API)</SelectItem>
</Select>
</FormControl>
)}
/>
</div>
{tokenReviewMode === "api" && (
<Controller
control={control}
name="tokenReviewerJwt"
render={({ field, fieldState: { error } }) => (
<FormControl
tooltipClassName="max-w-md"
label="Token Reviewer JWT"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="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."
>
<Input {...field} placeholder="" type="password" />
</FormControl>
)}
/>
)}
<Controller
control={control}
defaultValue=""
@@ -300,61 +417,6 @@ export const IdentityKubernetesAuthForm = ({
)}
/>
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
isOptional
>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={(v) => {
if (v !== "") {
onChange(v);
}
}}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewayLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(null)}
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
<Controller
control={control}
name="allowedNames"