misc: url and ssl config not needed when gateway auth

This commit is contained in:
Sheen Capadngan
2025-06-11 02:51:22 +08:00
parent f7fb015bd8
commit 8b443e0957
6 changed files with 263 additions and 172 deletions
@@ -20,6 +20,9 @@ import {
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000; const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
// This value is just a placeholder. When using gateway auth method, the url is irrelevant.
const GATEWAY_AUTH_DEFAULT_URL = "https://kubernetes.default.svc.cluster.local";
type TKubernetesProviderDTO = { type TKubernetesProviderDTO = {
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">; gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
}; };
@@ -37,7 +40,7 @@ const generateUsername = (usernameTemplate?: string | null) => {
export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO): TDynamicProviderFns => { export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => { const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretKubernetesSchema.parseAsync(inputs); const providerInputs = await DynamicSecretKubernetesSchema.parseAsync(inputs);
if (!providerInputs.gatewayId) { if (!providerInputs.gatewayId && providerInputs.url) {
await blockLocalAndPrivateIpAddresses(providerInputs.url); await blockLocalAndPrivateIpAddresses(providerInputs.url);
} }
@@ -272,7 +275,9 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
); );
}; };
const url = new URL(providerInputs.url); const rawUrl =
providerInputs.authMethod === KubernetesAuthMethod.Gateway ? GATEWAY_AUTH_DEFAULT_URL : providerInputs.url || "";
const url = new URL(rawUrl);
const k8sGatewayHost = url.hostname; const k8sGatewayHost = url.hostname;
const k8sPort = url.port ? Number(url.port) : 443; const k8sPort = url.port ? Number(url.port) : 443;
const k8sHost = `${url.protocol}//${url.hostname}`; const k8sHost = `${url.protocol}//${url.hostname}`;
@@ -488,7 +493,9 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
return { ...res.data, serviceAccountName: providerInputs.serviceAccountName }; return { ...res.data, serviceAccountName: providerInputs.serviceAccountName };
}; };
const url = new URL(providerInputs.url); const rawUrl =
providerInputs.authMethod === KubernetesAuthMethod.Gateway ? GATEWAY_AUTH_DEFAULT_URL : providerInputs.url || "";
const url = new URL(rawUrl);
const k8sHost = `${url.protocol}//${url.hostname}`; const k8sHost = `${url.protocol}//${url.hostname}`;
const k8sGatewayHost = url.hostname; const k8sGatewayHost = url.hostname;
const k8sPort = url.port ? Number(url.port) : 443; const k8sPort = url.port ? Number(url.port) : 443;
@@ -611,7 +618,12 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
}; };
if (providerInputs.credentialType === KubernetesCredentialType.Dynamic) { if (providerInputs.credentialType === KubernetesCredentialType.Dynamic) {
const url = new URL(providerInputs.url); const rawUrl =
providerInputs.authMethod === KubernetesAuthMethod.Gateway
? GATEWAY_AUTH_DEFAULT_URL
: providerInputs.url || "";
const url = new URL(rawUrl);
const k8sGatewayHost = url.hostname; const k8sGatewayHost = url.hostname;
const k8sPort = url.port ? Number(url.port) : 443; const k8sPort = url.port ? Number(url.port) : 443;
const k8sHost = `${url.protocol}//${url.hostname}`; const k8sHost = `${url.protocol}//${url.hostname}`;
@@ -1,3 +1,4 @@
import RE2 from "re2";
import { z } from "zod"; import { z } from "zod";
import { TDynamicSecretLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types"; import { TDynamicSecretLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types";
@@ -325,7 +326,12 @@ export const LdapSchema = z.union([
export const DynamicSecretKubernetesSchema = z export const DynamicSecretKubernetesSchema = z
.discriminatedUnion("credentialType", [ .discriminatedUnion("credentialType", [
z.object({ z.object({
url: z.string().url().trim().min(1), url: z
.string()
.optional()
.refine((val: string | undefined) => !val || new RE2(/^https?:\/\/.+/).test(val), {
message: "Invalid URL. Must start with http:// or https:// (e.g. https://example.com)"
}),
clusterToken: z.string().trim().optional(), clusterToken: z.string().trim().optional(),
ca: z.string().optional(), ca: z.string().optional(),
sslEnabled: z.boolean().default(false), sslEnabled: z.boolean().default(false),
@@ -341,7 +347,13 @@ export const DynamicSecretKubernetesSchema = z
authMethod: z.nativeEnum(KubernetesAuthMethod).default(KubernetesAuthMethod.Api) authMethod: z.nativeEnum(KubernetesAuthMethod).default(KubernetesAuthMethod.Api)
}), }),
z.object({ z.object({
url: z.string().url().trim().min(1), url: z
.string()
.url()
.optional()
.refine((val: string | undefined) => !val || new RE2(/^https?:\/\/.+/).test(val), {
message: "Invalid URL. Must start with http:// or https:// (e.g. https://example.com)"
}),
clusterToken: z.string().trim().optional(), clusterToken: z.string().trim().optional(),
ca: z.string().optional(), ca: z.string().optional(),
sslEnabled: z.boolean().default(false), sslEnabled: z.boolean().default(false),
@@ -369,13 +381,22 @@ export const DynamicSecretKubernetesSchema = z
message: "When auth method is set to Gateway, a gateway must be selected" message: "When auth method is set to Gateway, a gateway must be selected"
}); });
} }
if ((data.authMethod === KubernetesAuthMethod.Api || !data.authMethod) && !data.clusterToken) { if (data.authMethod === KubernetesAuthMethod.Api || !data.authMethod) {
if (!data.clusterToken) {
ctx.addIssue({ ctx.addIssue({
path: ["clusterToken"], path: ["clusterToken"],
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: "When auth method is set to Manual Token, a cluster token must be provided" message: "When auth method is set to Token, a cluster token must be provided"
}); });
} }
if (!data.url) {
ctx.addIssue({
path: ["url"],
code: z.ZodIssueCode.custom,
message: "When auth method is set to Token, a cluster URL must be provided"
});
}
}
}); });
export const DynamicSecretVerticaSchema = z.object({ export const DynamicSecretVerticaSchema = z.object({
@@ -162,6 +162,12 @@ This feature is ideal for scenarios where you need to:
tokens for the target service account. tokens for the target service account.
</Note> </Note>
<Note>
When using Gateway authentication, the Gateway will access the Kubernetes API server
using its internal cluster URL (typically https://kubernetes.default.svc) and TLS configuration.
You don't need to specify these values separately in the dynamic secret configuration.
</Note>
1. Deploy the Infisical Gateway in your cluster 1. Deploy the Infisical Gateway in your cluster
2. Set up RBAC permissions for the Gateway's service account: 2. Set up RBAC permissions for the Gateway's service account:
```yaml rbac.yaml ```yaml rbac.yaml
@@ -206,6 +212,7 @@ This feature is ideal for scenarios where you need to:
- Automatically clean up service accounts after token expiration - Automatically clean up service accounts after token expiration
- Assign different roles to different users or applications - Assign different roles to different users or applications
- Maintain strict control over service account permissions - Maintain strict control over service account permissions
- Support multiple namespaces with a single dynamic secret configuration
### Prerequisites ### Prerequisites
@@ -213,6 +220,16 @@ This feature is ideal for scenarios where you need to:
- Cluster access token with permissions to create service accounts and manage RBAC - Cluster access token with permissions to create service accounts and manage RBAC
- (Optional) [Gateway](/documentation/platform/gateways/overview) for private cluster access - (Optional) [Gateway](/documentation/platform/gateways/overview) for private cluster access
### Namespace Support
When configuring a dynamic secret, you can specify multiple allowed namespaces as a comma-separated list. During lease creation, you can then specify which namespace to use from this allowed list. This provides flexibility while maintaining security by:
- Allowing a single dynamic secret configuration to support multiple namespaces
- Restricting service account creation to only the specified allowed namespaces
- Enabling fine-grained control over which namespaces can be used for each lease
For example, if you configure a dynamic secret with allowed namespaces "default,kube-system,monitoring", you can create leases that use any of these namespaces while preventing access to other namespaces in your cluster.
### Authentication Setup ### Authentication Setup
Choose your authentication method: Choose your authentication method:
@@ -318,6 +335,12 @@ This feature is ideal for scenarios where you need to:
manage service accounts, their tokens, and RBAC resources. manage service accounts, their tokens, and RBAC resources.
</Note> </Note>
<Note>
When using Gateway authentication, the Gateway will access the Kubernetes API server
using its internal cluster URL (typically https://kubernetes.default.svc) and TLS configuration.
You don't need to specify these values separately in the dynamic secret configuration.
</Note>
1. Deploy the Infisical Gateway in your cluster 1. Deploy the Infisical Gateway in your cluster
2. Set up RBAC permissions for the Gateway's service account: 2. Set up RBAC permissions for the Gateway's service account:
```yaml rbac.yaml ```yaml rbac.yaml
@@ -401,13 +424,13 @@ This feature is ideal for scenarios where you need to:
Select a gateway for private cluster access. If not specified, the Internet Gateway will be used. Select a gateway for private cluster access. If not specified, the Internet Gateway will be used.
</ParamField> </ParamField>
<ParamField path="Cluster URL" type="string" required> <ParamField path="Cluster URL" type="string" required>
Kubernetes API server URL (e.g., https://kubernetes.default.svc) Kubernetes API server URL (e.g., https://kubernetes.default.svc). Not required when using Gateway authentication as the Gateway will use its internal cluster URL.
</ParamField> </ParamField>
<ParamField path="Enable SSL" type="boolean"> <ParamField path="Enable SSL" type="boolean">
Whether to enable SSL verification for the Kubernetes API server connection. Whether to enable SSL verification for the Kubernetes API server connection. Not required when using Gateway authentication as the Gateway will use its internal TLS configuration.
</ParamField> </ParamField>
<ParamField path="CA" type="string"> <ParamField path="CA" type="string">
Custom CA certificate for the Kubernetes API server. Leave blank to use the system/public CA. Custom CA certificate for the Kubernetes API server. Leave blank to use the system/public CA. Not required when using Gateway authentication as the Gateway will use its internal TLS configuration.
</ParamField> </ParamField>
<ParamField path="Auth Method" type="string" required> <ParamField path="Auth Method" type="string" required>
Choose between Token (API) or Gateway authentication. If using Gateway, the Gateway must be deployed in your Kubernetes cluster. Choose between Token (API) or Gateway authentication. If using Gateway, the Gateway must be deployed in your Kubernetes cluster.
@@ -418,18 +441,30 @@ This feature is ideal for scenarios where you need to:
<ParamField path="Credential Type" type="string" required> <ParamField path="Credential Type" type="string" required>
Choose between Static (predefined service account) or Dynamic (temporary service accounts with role assignments) Choose between Static (predefined service account) or Dynamic (temporary service accounts with role assignments)
</ParamField> </ParamField>
<Tabs>
<Tab title="Static Credentials Parameters">
<ParamField path="Service Account Name" type="string" required> <ParamField path="Service Account Name" type="string" required>
Name of the service account to generate tokens for (required for Static credentials) Name of the service account to generate tokens for
</ParamField> </ParamField>
<ParamField path="Namespace" type="string" required> <ParamField path="Namespace" type="string" required>
Kubernetes namespace where the service account exists or will be created Kubernetes namespace where the service account exists
</ParamField>
</Tab>
<Tab title="Dynamic Credentials Parameters">
<ParamField path="Allowed Namespaces" type="string" required>
Kubernetes namespace(s) where the service accounts will be created. You can specify multiple namespaces as a comma-separated list (e.g., "default,kube-system"). During lease creation, you can specify which namespace to use from this allowed list.
</ParamField> </ParamField>
<ParamField path="Role Type" type="string" required> <ParamField path="Role Type" type="string" required>
Type of role to assign (ClusterRole or Role) (required for Dynamic credentials) Type of role to assign (ClusterRole or Role)
</ParamField> </ParamField>
<ParamField path="Role" type="string" required> <ParamField path="Role" type="string" required>
Name of the role to assign to the temporary service account (required for Dynamic credentials) Name of the role to assign to the temporary service account
</ParamField> </ParamField>
</Tab>
</Tabs>
<ParamField path="Audiences" type="array"> <ParamField path="Audiences" type="array">
Optional list of audiences to include in the generated token Optional list of audiences to include in the generated token
</ParamField> </ParamField>
@@ -290,7 +290,7 @@ export type TDynamicSecretProvider =
type: DynamicSecretProviders.Kubernetes; type: DynamicSecretProviders.Kubernetes;
inputs: inputs:
| { | {
url: string; url?: string;
clusterToken?: string; clusterToken?: string;
ca?: string; ca?: string;
serviceAccountName: string; serviceAccountName: string;
@@ -302,7 +302,7 @@ export type TDynamicSecretProvider =
authMethod: string; authMethod: string;
} }
| { | {
url: string; url?: string;
clusterToken?: string; clusterToken?: string;
ca?: string; ca?: string;
credentialType: KubernetesDynamicSecretCredentialType.Dynamic; credentialType: KubernetesDynamicSecretCredentialType.Dynamic;
@@ -61,7 +61,7 @@ const formSchema = z
.object({ .object({
provider: z.discriminatedUnion("credentialType", [ provider: z.discriminatedUnion("credentialType", [
z.object({ z.object({
url: z.string().url().trim().min(1), url: z.string().trim().optional(),
clusterToken: z.string().trim().optional(), clusterToken: z.string().trim().optional(),
ca: z.string().optional(), ca: z.string().optional(),
sslEnabled: z.boolean().default(false), sslEnabled: z.boolean().default(false),
@@ -80,7 +80,7 @@ const formSchema = z
authMethod: z.nativeEnum(AuthMethod).default(AuthMethod.Api) authMethod: z.nativeEnum(AuthMethod).default(AuthMethod.Api)
}), }),
z.object({ z.object({
url: z.string().url().trim().min(1), url: z.string().trim().optional(),
clusterToken: z.string().trim().optional(), clusterToken: z.string().trim().optional(),
ca: z.string().optional(), ca: z.string().optional(),
sslEnabled: z.boolean().default(false), sslEnabled: z.boolean().default(false),
@@ -130,13 +130,22 @@ const formSchema = z
message: "When auth method is set to Gateway, a gateway must be selected" message: "When auth method is set to Gateway, a gateway must be selected"
}); });
} }
if (data.provider.authMethod === AuthMethod.Api && !data.provider.clusterToken) { if (data.provider.authMethod === AuthMethod.Api) {
if (!data.provider.clusterToken) {
ctx.addIssue({ ctx.addIssue({
path: ["provider.clusterToken"], path: ["provider.clusterToken"],
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: "When auth method is set to Token, a cluster token must be provided" message: "When auth method is set to Token, a cluster token must be provided"
}); });
} }
if (!data.provider.url) {
ctx.addIssue({
path: ["provider.url"],
code: z.ZodIssueCode.custom,
message: "When auth method is set to Token, a cluster URL must be provided"
});
}
}
}); });
type TForm = z.infer<typeof formSchema> & FieldValues; type TForm = z.infer<typeof formSchema> & FieldValues;
@@ -347,6 +356,32 @@ export const KubernetesInputForm = ({
)} )}
</OrgPermissionCan> </OrgPermissionCan>
</div> </div>
<Controller
control={control}
name="provider.authMethod"
defaultValue={AuthMethod.Api}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Auth Method"
isError={Boolean(error?.message)}
errorText={error?.message}
className="w-full"
tooltipText="Select the method of authentication. Token (API) uses a direct API token, while Gateway uses the service account of a Gateway deployed in a Kubernetes cluster to generate the service account token."
>
<Select
defaultValue={field.value}
{...field}
className="w-full"
onValueChange={(e) => field.onChange(e)}
>
<SelectItem value={AuthMethod.Api}>Token (API)</SelectItem>
<SelectItem value={AuthMethod.Gateway}>Gateway</SelectItem>
</Select>
</FormControl>
)}
/>
{authMethod === AuthMethod.Api && (
<>
<Controller <Controller
control={control} control={control}
name="provider.url" name="provider.url"
@@ -360,7 +395,6 @@ export const KubernetesInputForm = ({
</FormControl> </FormControl>
)} )}
/> />
<div className="mb-2 flex items-center"> <div className="mb-2 flex items-center">
<span className="mr-3 flex items-center text-sm text-mineshaft-400"> <span className="mr-3 flex items-center text-sm text-mineshaft-400">
Enable SSL Enable SSL
@@ -391,7 +425,6 @@ export const KubernetesInputForm = ({
)} )}
/> />
</div> </div>
<Controller <Controller
control={control} control={control}
name="provider.ca" name="provider.ca"
@@ -410,30 +443,8 @@ export const KubernetesInputForm = ({
</FormControl> </FormControl>
)} )}
/> />
<Controller </>
control={control}
name="provider.authMethod"
defaultValue={AuthMethod.Api}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Auth Method"
isError={Boolean(error?.message)}
errorText={error?.message}
className="w-full"
tooltipText="Select the method of authentication. Token (API) uses a direct API token, while Gateway uses the service account of a Gateway deployed in a Kubernetes cluster to generate the service account token."
>
<Select
defaultValue={field.value}
{...field}
className="w-full"
onValueChange={(e) => field.onChange(e)}
>
<SelectItem value={AuthMethod.Api}>Token (API)</SelectItem>
<SelectItem value={AuthMethod.Gateway}>Gateway</SelectItem>
</Select>
</FormControl>
)} )}
/>
{authMethod === AuthMethod.Api && ( {authMethod === AuthMethod.Api && (
<Controller <Controller
control={control} control={control}
@@ -59,7 +59,7 @@ const formSchema = z
.object({ .object({
inputs: z.discriminatedUnion("credentialType", [ inputs: z.discriminatedUnion("credentialType", [
z.object({ z.object({
url: z.string().url().trim().min(1), url: z.string().trim().optional(),
clusterToken: z.string().trim().optional(), clusterToken: z.string().trim().optional(),
ca: z.string().optional(), ca: z.string().optional(),
sslEnabled: z.boolean().default(false), sslEnabled: z.boolean().default(false),
@@ -78,7 +78,7 @@ const formSchema = z
authMethod: z.nativeEnum(AuthMethod).default(AuthMethod.Api) authMethod: z.nativeEnum(AuthMethod).default(AuthMethod.Api)
}), }),
z.object({ z.object({
url: z.string().url().trim().min(1), url: z.string().trim().optional(),
clusterToken: z.string().trim().optional(), clusterToken: z.string().trim().optional(),
ca: z.string().optional(), ca: z.string().optional(),
sslEnabled: z.boolean().default(false), sslEnabled: z.boolean().default(false),
@@ -127,13 +127,22 @@ const formSchema = z
message: "When auth method is set to Gateway, a gateway must be selected" message: "When auth method is set to Gateway, a gateway must be selected"
}); });
} }
if (data.inputs.authMethod === AuthMethod.Api && !data.inputs.clusterToken) { if (data.inputs.authMethod === AuthMethod.Api) {
if (!data.inputs.clusterToken) {
ctx.addIssue({ ctx.addIssue({
path: ["inputs.clusterToken"], path: ["inputs.clusterToken"],
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: "When auth method is set to Token, a cluster token must be provided" message: "When auth method is set to Token, a cluster token must be provided"
}); });
} }
if (!data.inputs.url) {
ctx.addIssue({
path: ["inputs.url"],
code: z.ZodIssueCode.custom,
message: "When auth method is set to Token, a cluster URL must be provided"
});
}
}
}); });
type TForm = z.infer<typeof formSchema> & FieldValues; type TForm = z.infer<typeof formSchema> & FieldValues;
@@ -342,6 +351,32 @@ export const EditDynamicSecretKubernetesForm = ({
)} )}
</OrgPermissionCan> </OrgPermissionCan>
</div> </div>
<Controller
control={control}
name="inputs.authMethod"
defaultValue={AuthMethod.Api}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Auth Method"
isError={Boolean(error?.message)}
errorText={error?.message}
className="w-full"
tooltipText="Select the method of authentication. Token (API) uses a direct API token, while Gateway uses the service account of a Gateway deployed in a Kubernetes cluster to generate the service account token."
>
<Select
defaultValue={field.value}
{...field}
className="w-full"
onValueChange={(e) => field.onChange(e)}
>
<SelectItem value={AuthMethod.Api}>Token (API)</SelectItem>
<SelectItem value={AuthMethod.Gateway}>Gateway</SelectItem>
</Select>
</FormControl>
)}
/>
{authMethod === AuthMethod.Api && (
<>
<Controller <Controller
control={control} control={control}
name="inputs.url" name="inputs.url"
@@ -363,8 +398,8 @@ export const EditDynamicSecretKubernetesForm = ({
className="ml-1 max-w-md" className="ml-1 max-w-md"
content={ content={
<span> <span>
If enabled, you can optionally provide a custom CA certificate. Leave If enabled, you can optionally provide a custom CA certificate.
blank to use the system/public CA. Leave blank to use the system/public CA.
</span> </span>
} }
> >
@@ -405,31 +440,8 @@ export const EditDynamicSecretKubernetesForm = ({
</FormControl> </FormControl>
)} )}
/> />
</>
<Controller
control={control}
name="inputs.authMethod"
defaultValue={AuthMethod.Api}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Auth Method"
isError={Boolean(error?.message)}
errorText={error?.message}
className="w-full"
tooltipText="Select the method of authentication. Token (API) uses a direct API token, while Gateway uses the service account of a Gateway deployed in a Kubernetes cluster to generate the service account token."
>
<Select
defaultValue={field.value}
{...field}
className="w-full"
onValueChange={(e) => field.onChange(e)}
>
<SelectItem value={AuthMethod.Api}>Token (API)</SelectItem>
<SelectItem value={AuthMethod.Gateway}>Gateway</SelectItem>
</Select>
</FormControl>
)} )}
/>
{authMethod === AuthMethod.Api && ( {authMethod === AuthMethod.Api && (
<Controller <Controller
control={control} control={control}