mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 22:28:15 +00:00
misc: filter optimizations
This commit is contained in:
@@ -332,6 +332,35 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/vault/auth-mounts",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
namespace: z.string(),
|
||||||
|
authType: z.string().optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
mounts: z.array(z.object({ path: z.string(), type: z.string() }))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const mounts = await server.services.migration.getVaultAuthMounts({
|
||||||
|
actor: req.permission,
|
||||||
|
namespace: req.query.namespace,
|
||||||
|
authType: req.query.authType
|
||||||
|
});
|
||||||
|
|
||||||
|
return { mounts };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/vault/import-secrets",
|
url: "/vault/import-secrets",
|
||||||
@@ -372,7 +401,8 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
namespace: z.string()
|
namespace: z.string(),
|
||||||
|
mountPath: z.string()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -384,7 +414,8 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const secretPaths = await server.services.migration.getVaultSecretPaths({
|
const secretPaths = await server.services.migration.getVaultSecretPaths({
|
||||||
actor: req.permission,
|
actor: req.permission,
|
||||||
namespace: req.query.namespace
|
namespace: req.query.namespace,
|
||||||
|
mountPath: req.query.mountPath
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secretPaths };
|
return { secretPaths };
|
||||||
@@ -399,7 +430,8 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
namespace: z.string()
|
namespace: z.string(),
|
||||||
|
mountPath: z.string()
|
||||||
}),
|
}),
|
||||||
|
|
||||||
response: {
|
response: {
|
||||||
@@ -437,7 +469,8 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const roles = await server.services.migration.getVaultKubernetesAuthRoles({
|
const roles = await server.services.migration.getVaultKubernetesAuthRoles({
|
||||||
actor: req.permission,
|
actor: req.permission,
|
||||||
namespace: req.query.namespace
|
namespace: req.query.namespace,
|
||||||
|
mountPath: req.query.mountPath
|
||||||
});
|
});
|
||||||
|
|
||||||
return { roles };
|
return { roles };
|
||||||
|
|||||||
@@ -455,7 +455,8 @@ export const listHCVaultMounts = async (
|
|||||||
export const listHCVaultSecretPaths = async (
|
export const listHCVaultSecretPaths = async (
|
||||||
namespace: string,
|
namespace: string,
|
||||||
connection: THCVaultConnection,
|
connection: THCVaultConnection,
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||||
|
filterMountPath?: string
|
||||||
) => {
|
) => {
|
||||||
const instanceUrl = await getHCVaultInstanceUrl(connection);
|
const instanceUrl = await getHCVaultInstanceUrl(connection);
|
||||||
const accessToken = await getHCVaultAccessToken(connection, gatewayService);
|
const accessToken = await getHCVaultAccessToken(connection, gatewayService);
|
||||||
@@ -532,7 +533,13 @@ export const listHCVaultSecretPaths = async (
|
|||||||
const mounts = await listHCVaultMounts(connection, gatewayService, namespace);
|
const mounts = await listHCVaultMounts(connection, gatewayService, namespace);
|
||||||
|
|
||||||
// Filter for KV mounts (kv, kv-v1, kv-v2)
|
// Filter for KV mounts (kv, kv-v1, kv-v2)
|
||||||
const kvMounts = mounts.filter((mount) => mount.type === "kv" || mount.type.startsWith("kv"));
|
let kvMounts = mounts.filter((mount) => mount.type === "kv" || mount.type.startsWith("kv"));
|
||||||
|
|
||||||
|
// If filterMountPath is provided, filter to only that mount
|
||||||
|
if (filterMountPath) {
|
||||||
|
const normalizedFilterPath = filterMountPath.replace(/\/$/, ""); // Remove trailing slash
|
||||||
|
kvMounts = kvMounts.filter((mount) => mount.path.replace(/\/$/, "") === normalizedFilterPath);
|
||||||
|
}
|
||||||
|
|
||||||
// Create concurrency limiter to avoid overwhelming the Vault instance
|
// Create concurrency limiter to avoid overwhelming the Vault instance
|
||||||
const limiter = createConcurrencyLimiter(HC_VAULT_CONCURRENCY_LIMIT);
|
const limiter = createConcurrencyLimiter(HC_VAULT_CONCURRENCY_LIMIT);
|
||||||
@@ -648,7 +655,7 @@ export const getHCVaultSecretsForPath = async (
|
|||||||
|
|
||||||
export const getHCVaultAuthMounts = async (
|
export const getHCVaultAuthMounts = async (
|
||||||
namespace: string,
|
namespace: string,
|
||||||
authType: HCVaultAuthType,
|
authType: HCVaultAuthType | undefined,
|
||||||
connection: THCVaultConnection,
|
connection: THCVaultConnection,
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||||
): Promise<THCVaultAuthMount[]> => {
|
): Promise<THCVaultAuthMount[]> => {
|
||||||
@@ -668,7 +675,8 @@ export const getHCVaultAuthMounts = async (
|
|||||||
const authMounts: THCVaultAuthMount[] = [];
|
const authMounts: THCVaultAuthMount[] = [];
|
||||||
|
|
||||||
Object.entries(data.data).forEach(([path, authMethod]) => {
|
Object.entries(data.data).forEach(([path, authMethod]) => {
|
||||||
if (authMethod.type === authType) {
|
// If authType is specified, filter by it; otherwise, include all
|
||||||
|
if (!authType || authMethod.type === authType) {
|
||||||
authMounts.push({
|
authMounts.push({
|
||||||
path,
|
path,
|
||||||
type: authMethod.type,
|
type: authMethod.type,
|
||||||
@@ -680,16 +688,17 @@ export const getHCVaultAuthMounts = async (
|
|||||||
|
|
||||||
return authMounts;
|
return authMounts;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error(error, `Unable to list HC Vault ${authType} auth mounts`);
|
const authTypeStr = authType || "all";
|
||||||
|
logger.error(error, `Unable to list HC Vault ${authTypeStr} auth mounts`);
|
||||||
|
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: `Failed to list ${authType} auth mounts: ${error.message || "Unknown error"}`
|
message: `Failed to list ${authTypeStr} auth mounts: ${error.message || "Unknown error"}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: `Unable to list ${authType} auth mounts from HashiCorp Vault`
|
message: `Unable to list ${authTypeStr} auth mounts from HashiCorp Vault`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -444,7 +444,15 @@ export const externalMigrationServiceFactory = ({
|
|||||||
return mounts;
|
return mounts;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVaultSecretPaths = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
|
const getVaultSecretPaths = async ({
|
||||||
|
actor,
|
||||||
|
namespace,
|
||||||
|
mountPath
|
||||||
|
}: {
|
||||||
|
actor: OrgServiceActor;
|
||||||
|
namespace: string;
|
||||||
|
mountPath: string;
|
||||||
|
}) => {
|
||||||
const { hasRole } = await permissionService.getOrgPermission(
|
const { hasRole } = await permissionService.getOrgPermission(
|
||||||
actor.type,
|
actor.type,
|
||||||
actor.id,
|
actor.id,
|
||||||
@@ -482,7 +490,7 @@ export const externalMigrationServiceFactory = ({
|
|||||||
credentials
|
credentials
|
||||||
} as THCVaultConnection;
|
} as THCVaultConnection;
|
||||||
|
|
||||||
const secretPaths = await listHCVaultSecretPaths(namespace, connection, gatewayService);
|
const secretPaths = await listHCVaultSecretPaths(namespace, connection, gatewayService, mountPath);
|
||||||
|
|
||||||
return secretPaths;
|
return secretPaths;
|
||||||
};
|
};
|
||||||
@@ -617,7 +625,66 @@ export const externalMigrationServiceFactory = ({
|
|||||||
return deletedConfig;
|
return deletedConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVaultKubernetesAuthRoles = async ({ actor, namespace }: { actor: OrgServiceActor; namespace: string }) => {
|
const getVaultAuthMounts = async ({
|
||||||
|
actor,
|
||||||
|
namespace,
|
||||||
|
authType
|
||||||
|
}: {
|
||||||
|
actor: OrgServiceActor;
|
||||||
|
namespace: string;
|
||||||
|
authType?: string;
|
||||||
|
}) => {
|
||||||
|
const { hasRole } = await permissionService.getOrgPermission(
|
||||||
|
actor.type,
|
||||||
|
actor.id,
|
||||||
|
actor.orgId,
|
||||||
|
actor.authMethod,
|
||||||
|
actor.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasRole(OrgMembershipRole.Admin)) {
|
||||||
|
throw new ForbiddenRequestError({ message: "Only admins can view vault auth mounts" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({
|
||||||
|
orgId: actor.orgId,
|
||||||
|
namespace
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!vaultConfig) {
|
||||||
|
throw new NotFoundError({ message: "Vault migration config not found for this namespace" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vaultConfig.connection) {
|
||||||
|
throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = await decryptAppConnectionCredentials({
|
||||||
|
orgId: vaultConfig.orgId,
|
||||||
|
encryptedCredentials: vaultConfig.connection.encryptedCredentials,
|
||||||
|
kmsService,
|
||||||
|
projectId: null
|
||||||
|
});
|
||||||
|
|
||||||
|
const connection = {
|
||||||
|
...vaultConfig.connection,
|
||||||
|
credentials
|
||||||
|
} as THCVaultConnection;
|
||||||
|
|
||||||
|
const authMounts = await getHCVaultAuthMounts(namespace, authType as HCVaultAuthType, connection, gatewayService);
|
||||||
|
|
||||||
|
return authMounts;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVaultKubernetesAuthRoles = async ({
|
||||||
|
actor,
|
||||||
|
namespace,
|
||||||
|
mountPath
|
||||||
|
}: {
|
||||||
|
actor: OrgServiceActor;
|
||||||
|
namespace: string;
|
||||||
|
mountPath: string;
|
||||||
|
}) => {
|
||||||
const { hasRole } = await permissionService.getOrgPermission(
|
const { hasRole } = await permissionService.getOrgPermission(
|
||||||
actor.type,
|
actor.type,
|
||||||
actor.id,
|
actor.id,
|
||||||
@@ -655,18 +722,10 @@ export const externalMigrationServiceFactory = ({
|
|||||||
credentials
|
credentials
|
||||||
} as THCVaultConnection;
|
} as THCVaultConnection;
|
||||||
|
|
||||||
// Get all Kubernetes auth mounts for this namespace
|
// Get roles for the specified mount path only
|
||||||
const authMounts = await getHCVaultAuthMounts(namespace, HCVaultAuthType.Kubernetes, connection, gatewayService);
|
const roles = await getHCVaultKubernetesAuthRoles(namespace, mountPath, connection, gatewayService);
|
||||||
|
|
||||||
// For each mount, get all roles with their configuration
|
return roles;
|
||||||
const allRolesPromises = authMounts.map(async (mount) => {
|
|
||||||
const roles = await getHCVaultKubernetesAuthRoles(namespace, mount.path, connection, gatewayService);
|
|
||||||
return roles;
|
|
||||||
});
|
|
||||||
|
|
||||||
const rolesPerMount = await Promise.all(allRolesPromises);
|
|
||||||
|
|
||||||
return rolesPerMount.flat();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -680,6 +739,7 @@ export const externalMigrationServiceFactory = ({
|
|||||||
getVaultNamespaces,
|
getVaultNamespaces,
|
||||||
getVaultPolicies,
|
getVaultPolicies,
|
||||||
getVaultMounts,
|
getVaultMounts,
|
||||||
|
getVaultAuthMounts,
|
||||||
getVaultSecretPaths,
|
getVaultSecretPaths,
|
||||||
importVaultSecrets,
|
importVaultSecrets,
|
||||||
getVaultKubernetesAuthRoles
|
getVaultKubernetesAuthRoles
|
||||||
|
|||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 432 KiB After Width: | Height: | Size: 429 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 489 KiB After Width: | Height: | Size: 505 KiB |
@@ -17,8 +17,21 @@ export const externalMigrationQueryKeys = {
|
|||||||
vaultNamespaces: () => ["vault-namespaces"],
|
vaultNamespaces: () => ["vault-namespaces"],
|
||||||
vaultPolicies: (namespace?: string) => ["vault-policies", namespace],
|
vaultPolicies: (namespace?: string) => ["vault-policies", namespace],
|
||||||
vaultMounts: (namespace?: string) => ["vault-mounts", namespace],
|
vaultMounts: (namespace?: string) => ["vault-mounts", namespace],
|
||||||
vaultSecretPaths: (namespace?: string) => ["vault-secret-paths", namespace],
|
vaultAuthMounts: (namespace?: string, authType?: string) => [
|
||||||
vaultKubernetesAuthRoles: (namespace?: string) => ["vault-kubernetes-auth-roles", namespace]
|
"vault-auth-mounts",
|
||||||
|
namespace,
|
||||||
|
authType
|
||||||
|
],
|
||||||
|
vaultSecretPaths: (namespace?: string, mountPath?: string) => [
|
||||||
|
"vault-secret-paths",
|
||||||
|
namespace,
|
||||||
|
mountPath
|
||||||
|
],
|
||||||
|
vaultKubernetesAuthRoles: (namespace?: string, mountPath?: string) => [
|
||||||
|
"vault-kubernetes-auth-roles",
|
||||||
|
namespace,
|
||||||
|
mountPath
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useHasCustomMigrationAvailable = (provider: ExternalMigrationProviders) => {
|
export const useHasCustomMigrationAvailable = (provider: ExternalMigrationProviders) => {
|
||||||
@@ -91,38 +104,71 @@ export const useGetVaultMounts = (enabled = true, namespace?: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetVaultSecretPaths = (enabled = true, namespace?: string) => {
|
export const useGetVaultSecretPaths = (enabled = true, namespace?: string, mountPath?: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: externalMigrationQueryKeys.vaultSecretPaths(namespace),
|
queryKey: externalMigrationQueryKeys.vaultSecretPaths(namespace, mountPath),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
if (!namespace || !mountPath) {
|
||||||
|
throw new Error("Both namespace and mountPath are required");
|
||||||
|
}
|
||||||
|
|
||||||
const { data } = await apiRequest.get<{
|
const { data } = await apiRequest.get<{
|
||||||
secretPaths: string[];
|
secretPaths: string[];
|
||||||
}>("/api/v3/external-migration/vault/secret-paths", {
|
}>("/api/v3/external-migration/vault/secret-paths", {
|
||||||
params: {
|
params: {
|
||||||
namespace
|
namespace,
|
||||||
|
mountPath
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.secretPaths;
|
return data.secretPaths;
|
||||||
},
|
},
|
||||||
|
enabled: enabled && !!namespace && !!mountPath
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetVaultAuthMounts = (enabled = true, namespace?: string, authType?: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: externalMigrationQueryKeys.vaultAuthMounts(namespace, authType),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<{
|
||||||
|
mounts: Array<{ path: string; type: string }>;
|
||||||
|
}>("/api/v3/external-migration/vault/auth-mounts", {
|
||||||
|
params: {
|
||||||
|
namespace,
|
||||||
|
...(authType && { authType })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.mounts;
|
||||||
|
},
|
||||||
enabled
|
enabled
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetVaultKubernetesAuthRoles = (enabled = true, namespace?: string) => {
|
export const useGetVaultKubernetesAuthRoles = (
|
||||||
|
enabled = true,
|
||||||
|
namespace?: string,
|
||||||
|
mountPath?: string
|
||||||
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: externalMigrationQueryKeys.vaultKubernetesAuthRoles(namespace),
|
queryKey: externalMigrationQueryKeys.vaultKubernetesAuthRoles(namespace, mountPath),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
if (!namespace || !mountPath) {
|
||||||
|
throw new Error("Both namespace and mountPath are required");
|
||||||
|
}
|
||||||
|
|
||||||
const { data } = await apiRequest.get<{
|
const { data } = await apiRequest.get<{
|
||||||
roles: VaultKubernetesAuthRole[];
|
roles: VaultKubernetesAuthRole[];
|
||||||
}>("/api/v3/external-migration/vault/auth-roles/kubernetes", {
|
}>("/api/v3/external-migration/vault/auth-roles/kubernetes", {
|
||||||
params: {
|
params: {
|
||||||
namespace
|
namespace,
|
||||||
|
mountPath
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return data.roles;
|
return data.roles;
|
||||||
},
|
},
|
||||||
enabled
|
enabled: enabled && !!namespace && !!mountPath
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
+65
-6
@@ -10,6 +10,7 @@ import {
|
|||||||
ModalContent
|
ModalContent
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
|
useGetVaultAuthMounts,
|
||||||
useGetVaultKubernetesAuthRoles,
|
useGetVaultKubernetesAuthRoles,
|
||||||
useGetVaultNamespaces
|
useGetVaultNamespaces
|
||||||
} from "@app/hooks/api/migration/queries";
|
} from "@app/hooks/api/migration/queries";
|
||||||
@@ -28,21 +29,39 @@ type ContentProps = {
|
|||||||
|
|
||||||
const Content = ({ onClose, onImport }: ContentProps) => {
|
const Content = ({ onClose, onImport }: ContentProps) => {
|
||||||
const [selectedNamespace, setSelectedNamespace] = useState<string | null>(null);
|
const [selectedNamespace, setSelectedNamespace] = useState<string | null>(null);
|
||||||
|
const [selectedMountPath, setSelectedMountPath] = useState<string | null>(null);
|
||||||
const [selectedRole, setSelectedRole] = useState<VaultKubernetesAuthRole | null>(null);
|
const [selectedRole, setSelectedRole] = useState<VaultKubernetesAuthRole | null>(null);
|
||||||
const [shouldFetchRoles, setShouldFetchRoles] = useState(false);
|
const [shouldFetchRoles, setShouldFetchRoles] = useState(false);
|
||||||
|
const [shouldFetchMounts, setShouldFetchMounts] = useState(false);
|
||||||
|
|
||||||
const { data: namespaces, isLoading: isLoadingNamespaces } = useGetVaultNamespaces();
|
const { data: namespaces, isLoading: isLoadingNamespaces } = useGetVaultNamespaces();
|
||||||
|
const { data: authMounts, isLoading: isLoadingMounts } = useGetVaultAuthMounts(
|
||||||
|
shouldFetchMounts,
|
||||||
|
selectedNamespace ?? undefined,
|
||||||
|
"kubernetes"
|
||||||
|
);
|
||||||
const { data: roles, isLoading: isLoadingRoles } = useGetVaultKubernetesAuthRoles(
|
const { data: roles, isLoading: isLoadingRoles } = useGetVaultKubernetesAuthRoles(
|
||||||
shouldFetchRoles,
|
shouldFetchRoles,
|
||||||
selectedNamespace ?? undefined
|
selectedNamespace ?? undefined,
|
||||||
|
selectedMountPath ?? undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Enable fetching mounts when namespace is selected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedNamespace) {
|
if (selectedNamespace) {
|
||||||
setShouldFetchRoles(true);
|
setShouldFetchMounts(true);
|
||||||
}
|
}
|
||||||
}, [selectedNamespace]);
|
}, [selectedNamespace]);
|
||||||
|
|
||||||
|
// Enable fetching roles when both namespace and mount path are selected
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedNamespace && selectedMountPath) {
|
||||||
|
setShouldFetchRoles(true);
|
||||||
|
} else {
|
||||||
|
setShouldFetchRoles(false);
|
||||||
|
}
|
||||||
|
}, [selectedNamespace, selectedMountPath]);
|
||||||
|
|
||||||
const handleImportAndApply = () => {
|
const handleImportAndApply = () => {
|
||||||
if (!selectedRole) {
|
if (!selectedRole) {
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -70,6 +89,7 @@ const Content = ({ onClose, onImport }: ContentProps) => {
|
|||||||
if (value && !Array.isArray(value)) {
|
if (value && !Array.isArray(value)) {
|
||||||
const namespace = value as { id: string; name: string };
|
const namespace = value as { id: string; name: string };
|
||||||
setSelectedNamespace(namespace.name);
|
setSelectedNamespace(namespace.name);
|
||||||
|
setSelectedMountPath(null);
|
||||||
setSelectedRole(null);
|
setSelectedRole(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -81,7 +101,42 @@ const Content = ({ onClose, onImport }: ContentProps) => {
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-mineshaft-400">
|
<p className="mt-1 text-xs text-mineshaft-400">
|
||||||
Select the Vault namespace to fetch available Kubernetes auth roles
|
Select the Vault namespace to fetch available auth mounts
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl
|
||||||
|
label="Auth Engine"
|
||||||
|
className="mb-4"
|
||||||
|
tooltipText="Select the Kubernetes auth engine to narrow down available roles."
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<FilterableSelect
|
||||||
|
value={
|
||||||
|
selectedMountPath
|
||||||
|
? authMounts?.find((mount) => mount.path === selectedMountPath)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (value && !Array.isArray(value)) {
|
||||||
|
const mount = value as { path: string; type: string };
|
||||||
|
setSelectedMountPath(mount.path.replace(/\/$/, "")); // Remove trailing slash
|
||||||
|
setSelectedRole(null);
|
||||||
|
} else {
|
||||||
|
setSelectedMountPath(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={authMounts || []}
|
||||||
|
getOptionValue={(option) => option.path}
|
||||||
|
getOptionLabel={(option) => option.path.replace(/\/$/, "")}
|
||||||
|
isDisabled={isLoadingMounts || !authMounts?.length}
|
||||||
|
placeholder="Select auth engine..."
|
||||||
|
isClearable
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-mineshaft-400">
|
||||||
|
Choose a Kubernetes auth engine to filter available roles
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -99,9 +154,13 @@ const Content = ({ onClose, onImport }: ContentProps) => {
|
|||||||
}}
|
}}
|
||||||
options={roles || []}
|
options={roles || []}
|
||||||
getOptionValue={(option) => option.name}
|
getOptionValue={(option) => option.name}
|
||||||
getOptionLabel={(option) => `${option.name} (${option.mountPath})`}
|
getOptionLabel={(option) => option.name}
|
||||||
isDisabled={isLoadingRoles || !roles?.length}
|
isDisabled={isLoadingRoles || !roles?.length || !selectedMountPath}
|
||||||
placeholder="Select a Kubernetes role to load..."
|
placeholder={
|
||||||
|
!selectedMountPath
|
||||||
|
? "Select an auth engine first..."
|
||||||
|
: "Select a Kubernetes role to load..."
|
||||||
|
}
|
||||||
isClearable
|
isClearable
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
|
|||||||
+53
-7
@@ -34,6 +34,7 @@ type ContentProps = {
|
|||||||
|
|
||||||
const Content = ({ onClose, environment, secretPath, onImport }: ContentProps) => {
|
const Content = ({ onClose, environment, secretPath, onImport }: ContentProps) => {
|
||||||
const [selectedNamespace, setSelectedNamespace] = useState<string | null>(null);
|
const [selectedNamespace, setSelectedNamespace] = useState<string | null>(null);
|
||||||
|
const [selectedMountPath, setSelectedMountPath] = useState<string | null>(null);
|
||||||
const [selectedPath, setSelectedPath] = useState<string | null>(null);
|
const [selectedPath, setSelectedPath] = useState<string | null>(null);
|
||||||
const [shouldFetchPaths, setShouldFetchPaths] = useState(false);
|
const [shouldFetchPaths, setShouldFetchPaths] = useState(false);
|
||||||
const [shouldFetchMounts, setShouldFetchMounts] = useState(false);
|
const [shouldFetchMounts, setShouldFetchMounts] = useState(false);
|
||||||
@@ -41,21 +42,33 @@ const Content = ({ onClose, environment, secretPath, onImport }: ContentProps) =
|
|||||||
const { data: namespaces, isLoading: isLoadingNamespaces } = useGetVaultNamespaces();
|
const { data: namespaces, isLoading: isLoadingNamespaces } = useGetVaultNamespaces();
|
||||||
const { data: secretPaths, isLoading: isLoadingPaths } = useGetVaultSecretPaths(
|
const { data: secretPaths, isLoading: isLoadingPaths } = useGetVaultSecretPaths(
|
||||||
shouldFetchPaths,
|
shouldFetchPaths,
|
||||||
selectedNamespace ?? undefined
|
selectedNamespace ?? undefined,
|
||||||
|
selectedMountPath ?? undefined
|
||||||
);
|
);
|
||||||
const { data: mounts, isLoading: isLoadingMounts } = useGetVaultMounts(
|
const { data: mounts, isLoading: isLoadingMounts } = useGetVaultMounts(
|
||||||
shouldFetchMounts,
|
shouldFetchMounts,
|
||||||
selectedNamespace ?? undefined
|
selectedNamespace ?? undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
// Enable fetching paths and mounts when namespace is selected
|
// Filter to only show KV mounts
|
||||||
|
const kvMounts = mounts?.filter((mount) => mount.type === "kv" || mount.type.startsWith("kv"));
|
||||||
|
|
||||||
|
// Enable fetching mounts when namespace is selected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedNamespace) {
|
if (selectedNamespace) {
|
||||||
setShouldFetchPaths(true);
|
|
||||||
setShouldFetchMounts(true);
|
setShouldFetchMounts(true);
|
||||||
}
|
}
|
||||||
}, [selectedNamespace]);
|
}, [selectedNamespace]);
|
||||||
|
|
||||||
|
// Enable fetching paths when both namespace and mount path are selected
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedNamespace && selectedMountPath) {
|
||||||
|
setShouldFetchPaths(true);
|
||||||
|
} else {
|
||||||
|
setShouldFetchPaths(false);
|
||||||
|
}
|
||||||
|
}, [selectedNamespace, selectedMountPath]);
|
||||||
|
|
||||||
const handleImport = () => {
|
const handleImport = () => {
|
||||||
if (!selectedPath) {
|
if (!selectedPath) {
|
||||||
createNotification({ type: "error", text: "Please select a Vault secret path to import" });
|
createNotification({ type: "error", text: "Please select a Vault secret path to import" });
|
||||||
@@ -111,6 +124,7 @@ const Content = ({ onClose, environment, secretPath, onImport }: ContentProps) =
|
|||||||
if (value && !Array.isArray(value)) {
|
if (value && !Array.isArray(value)) {
|
||||||
const namespace = value as { id: string; name: string };
|
const namespace = value as { id: string; name: string };
|
||||||
setSelectedNamespace(namespace.name);
|
setSelectedNamespace(namespace.name);
|
||||||
|
setSelectedMountPath(null);
|
||||||
setSelectedPath(null);
|
setSelectedPath(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -122,7 +136,35 @@ const Content = ({ onClose, environment, secretPath, onImport }: ContentProps) =
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-mineshaft-400">
|
<p className="mt-1 text-xs text-mineshaft-400">
|
||||||
Select the Vault namespace to fetch available secret paths
|
Select the Vault namespace to fetch available mounts
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl
|
||||||
|
label="Secrets Engine"
|
||||||
|
className="mb-4"
|
||||||
|
tooltipText="Select the KV secrets engine to narrow down secret paths."
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<FilterableSelect
|
||||||
|
value={kvMounts?.find((mount) => mount.path === selectedMountPath)}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (value && !Array.isArray(value)) {
|
||||||
|
const mount = value as { path: string; type: string; version: string | null };
|
||||||
|
setSelectedMountPath(mount.path.replace(/\/$/, "")); // Remove trailing slash
|
||||||
|
setSelectedPath(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={kvMounts || []}
|
||||||
|
getOptionValue={(option) => option.path}
|
||||||
|
getOptionLabel={(option) => option.path.replace(/\/$/, "")}
|
||||||
|
isDisabled={isLoadingMounts || !kvMounts?.length}
|
||||||
|
placeholder="Select secrets engine..."
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-mineshaft-400">
|
||||||
|
Choose a KV secrets engine to filter available secret paths
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -141,13 +183,17 @@ const Content = ({ onClose, environment, secretPath, onImport }: ContentProps) =
|
|||||||
options={(secretPaths || []).map((path) => ({ path }))}
|
options={(secretPaths || []).map((path) => ({ path }))}
|
||||||
getOptionValue={(option) => option.path}
|
getOptionValue={(option) => option.path}
|
||||||
getOptionLabel={(option) => option.path}
|
getOptionLabel={(option) => option.path}
|
||||||
isDisabled={isLoadingPaths || !secretPaths?.length}
|
isDisabled={isLoadingPaths || !secretPaths?.length || !selectedMountPath}
|
||||||
placeholder="Select a Vault path to import..."
|
placeholder={
|
||||||
|
!selectedMountPath
|
||||||
|
? "Select a mount path first..."
|
||||||
|
: "Select a Vault path to import..."
|
||||||
|
}
|
||||||
isClearable
|
isClearable
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-mineshaft-400">
|
<p className="mt-1 text-xs text-mineshaft-400">
|
||||||
Choose a secret path from your Vault namespace to import into Infisical
|
Choose a secret path from the selected mount to import into Infisical
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
Reference in New Issue
Block a user