From 091e5211809f53cd6fb5a85a68c78bc5e097a883 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 12 May 2025 14:49:45 -0400 Subject: [PATCH] review fixes --- .../20250508210717_identity-oci-auth.ts | 2 +- backend/src/db/schemas/identity-oci-auths.ts | 2 +- .../ee/services/audit-log/audit-log-types.ts | 4 +- .../routes/v1/identity-oci-auth-router.ts | 7 +-- .../identity-oci-auth-service.ts | 15 +++++-- .../identity-oci-auth-types.ts | 6 +-- .../identity-oci-auth-validators.ts | 12 +++--- .../platform/identities/oci-auth.mdx | 43 +++++++++++++++++++ frontend/src/hooks/api/identities/types.ts | 6 +-- .../IdentitySection/IdentityOciAuthForm.tsx | 17 +++++--- .../pages/organization/SettingsPage/route.tsx | 38 ++++++++-------- 11 files changed, 104 insertions(+), 48 deletions(-) diff --git a/backend/src/db/migrations/20250508210717_identity-oci-auth.ts b/backend/src/db/migrations/20250508210717_identity-oci-auth.ts index 958f61ae2..9512807d1 100644 --- a/backend/src/db/migrations/20250508210717_identity-oci-auth.ts +++ b/backend/src/db/migrations/20250508210717_identity-oci-auth.ts @@ -17,7 +17,7 @@ export async function up(knex: Knex): Promise { t.string("type").notNullable(); t.string("tenancyOcid").notNullable(); - t.string("allowedUsernames").notNullable(); + t.string("allowedUsernames").nullable(); }); } diff --git a/backend/src/db/schemas/identity-oci-auths.ts b/backend/src/db/schemas/identity-oci-auths.ts index 045f42795..e0be86b78 100644 --- a/backend/src/db/schemas/identity-oci-auths.ts +++ b/backend/src/db/schemas/identity-oci-auths.ts @@ -18,7 +18,7 @@ export const IdentityOciAuthsSchema = z.object({ identityId: z.string().uuid(), type: z.string(), tenancyOcid: z.string(), - allowedUsernames: z.string() + allowedUsernames: z.string().nullable().optional() }); export type TIdentityOciAuths = z.infer; diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index cb1143e60..65021a253 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -1022,7 +1022,7 @@ interface AddIdentityOciAuthEvent { metadata: { identityId: string; tenancyOcid: string; - allowedUsernames: string; + allowedUsernames: string | null; accessTokenTTL: number; accessTokenMaxTTL: number; accessTokenNumUsesLimit: number; @@ -1042,7 +1042,7 @@ interface UpdateIdentityOciAuthEvent { metadata: { identityId: string; tenancyOcid?: string; - allowedUsernames?: string; + allowedUsernames: string | null; accessTokenTTL?: number; accessTokenMaxTTL?: number; accessTokenNumUsesLimit?: number; diff --git a/backend/src/server/routes/v1/identity-oci-auth-router.ts b/backend/src/server/routes/v1/identity-oci-auth-router.ts index a5f330143..de9866c85 100644 --- a/backend/src/server/routes/v1/identity-oci-auth-router.ts +++ b/backend/src/server/routes/v1/identity-oci-auth-router.ts @@ -143,7 +143,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider) metadata: { identityId: identityOciAuth.identityId, tenancyOcid: identityOciAuth.tenancyOcid, - allowedUsernames: identityOciAuth.allowedUsernames, + allowedUsernames: identityOciAuth.allowedUsernames || null, accessTokenTTL: identityOciAuth.accessTokenTTL, accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL, accessTokenTrustedIps: identityOciAuth.accessTokenTrustedIps as TIdentityTrustedIp[], @@ -214,7 +214,8 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider) actorAuthMethod: req.permission.authMethod, actorOrgId: req.permission.orgId, ...req.body, - identityId: req.params.identityId + identityId: req.params.identityId, + allowedUsernames: req.body.allowedUsernames || null }); await server.services.auditLog.createAuditLog({ @@ -225,7 +226,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider) metadata: { identityId: identityOciAuth.identityId, tenancyOcid: identityOciAuth.tenancyOcid, - allowedUsernames: identityOciAuth.allowedUsernames, + allowedUsernames: identityOciAuth.allowedUsernames || null, accessTokenTTL: identityOciAuth.accessTokenTTL, accessTokenMaxTTL: identityOciAuth.accessTokenMaxTTL, accessTokenTrustedIps: identityOciAuth.accessTokenTrustedIps as TIdentityTrustedIp[], diff --git a/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts b/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts index 55561f3db..f3df97330 100644 --- a/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts +++ b/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ForbiddenError } from "@casl/ability"; +import { AxiosError } from "axios"; import jwt from "jsonwebtoken"; import RE2 from "re2"; @@ -15,6 +16,7 @@ import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; +import { logger } from "@app/lib/logger"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { ActorType, AuthTokenType } from "../auth/auth-type"; @@ -60,15 +62,20 @@ export const identityOciAuthServiceFactory = ({ await blockLocalAndPrivateIpAddresses(headers.host); // Validate OCI host format - if (!headers.host || !new RE2("^identity\\.[a-zA-Z0-9-]+\\.oraclecloud\\.com$").test(headers.host)) { + if (!headers.host || !new RE2("^identity\\.([a-z]{2}-[a-z]+-[1-9])\\.oraclecloud\\.com$").test(headers.host)) { throw new BadRequestError({ message: "Invalid OCI host format. Expected format: identity..oraclecloud.com" }); } - const { data } = await request.get(`https://${headers.host}/20160918/users/${userOcid}`, { - headers - }); + const { data } = await request + .get(`https://${headers.host}/20160918/users/${userOcid}`, { + headers + }) + .catch((err: AxiosError) => { + logger.error(err.response, "OciIdentityLogin: Failed to authenticate with Oracle Cloud"); + throw err; + }); if (data.compartmentId !== identityOciAuth.tenancyOcid) { throw new UnauthorizedError({ diff --git a/backend/src/services/identity-oci-auth/identity-oci-auth-types.ts b/backend/src/services/identity-oci-auth/identity-oci-auth-types.ts index 25b59e4bf..c7a131bde 100644 --- a/backend/src/services/identity-oci-auth/identity-oci-auth-types.ts +++ b/backend/src/services/identity-oci-auth/identity-oci-auth-types.ts @@ -13,7 +13,7 @@ export type TLoginOciAuthDTO = { export type TAttachOciAuthDTO = { identityId: string; tenancyOcid: string; - allowedUsernames: string; + allowedUsernames: string | null; accessTokenTTL: number; accessTokenMaxTTL: number; accessTokenNumUsesLimit: number; @@ -23,8 +23,8 @@ export type TAttachOciAuthDTO = { export type TUpdateOciAuthDTO = { identityId: string; - tenancyOcid?: string; - allowedUsernames?: string; + tenancyOcid: string; + allowedUsernames: string | null; accessTokenTTL?: number; accessTokenMaxTTL?: number; accessTokenNumUsesLimit?: number; diff --git a/backend/src/services/identity-oci-auth/identity-oci-auth-validators.ts b/backend/src/services/identity-oci-auth/identity-oci-auth-validators.ts index d881014b3..49100b46c 100644 --- a/backend/src/services/identity-oci-auth/identity-oci-auth-validators.ts +++ b/backend/src/services/identity-oci-auth/identity-oci-auth-validators.ts @@ -9,16 +9,18 @@ export const validateUsernames = z .string() .trim() .max(500, "Input exceeds the maximum limit of 500 characters") - .transform((val) => - val + .nullish() + .transform((val) => { + if (!val) return []; + return val .split(",") .map((s) => s.trim()) - .filter(Boolean) - ) + .filter(Boolean); + }) .refine((arr) => arr.every((name) => usernameSchema.safeParse(name).success), { message: "One or more usernames are invalid" }) - .transform((arr) => arr.join(", ")); + .transform((arr) => (arr.length > 0 ? arr.join(", ") : null)); export const validateTenancy = z .string() diff --git a/docs/documentation/platform/identities/oci-auth.mdx b/docs/documentation/platform/identities/oci-auth.mdx index 536e1846b..6725260f5 100644 --- a/docs/documentation/platform/identities/oci-auth.mdx +++ b/docs/documentation/platform/identities/oci-auth.mdx @@ -43,6 +43,49 @@ To be more specific: 4. Infisical checks the user's properties against set criteria such as **Allowed Usernames** and **Tenancy OCID**. 5. If all checks pass, Infisical returns a short-lived access token that the client can use to make authenticated requests to the Infisical API. +## Prerequisite + +In order to sign requests, you must have an OCI user with credentials such as the private key. If you're unaware of how to create a user and obtain the needed credentials, expand the menu below. + + + + + ![Search Domains](/images/app-connections/oci/search-domains.png) + + + Select the domain in which you want to create the Infisical user account. + + ![Select Domain](/images/app-connections/oci/select-domain.png) + + + ![Select Users](/images/app-connections/oci/select-users.png) + + + ![Click Create User](/images/app-connections/oci/click-create-user.png) + + + The name, email, and username can be anything. + + ![Create User](/images/app-connections/oci/create-user.png) + + + After you've created a user, you'll be redirected to the user's page. Navigate to 'API keys'. + + ![Select API Keys](/images/app-connections/oci/select-api-keys.png) + + + Click on 'Add API key' and then download or import the private key. After you've obtained the private key, click 'Add'. + + ![Add API Key](/images/app-connections/oci/add-api-key.png) + + + After creating the API key, you'll be shown a modal with relevant information. Save the highlighted values (and the private key) for later steps. + + ![User Info](/images/app-connections/oci/user-info.png) + + + + ## Guide In the following steps, we explore how to create and use identities for your workloads and applications on OCI to diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index 59c288309..df6a01499 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -294,7 +294,7 @@ export type IdentityOciAuth = { identityId: string; type: "iam"; tenancyOcid: string; - allowedUsernames: string; + allowedUsernames?: string | null; accessTokenTTL: number; accessTokenMaxTTL: number; accessTokenNumUsesLimit: number; @@ -305,7 +305,7 @@ export type AddIdentityOciAuthDTO = { organizationId: string; identityId: string; tenancyOcid: string; - allowedUsernames: string; + allowedUsernames?: string | null; accessTokenTTL: number; accessTokenMaxTTL: number; accessTokenNumUsesLimit: number; @@ -318,7 +318,7 @@ export type UpdateIdentityOciAuthDTO = { organizationId: string; identityId: string; tenancyOcid?: string; - allowedUsernames?: string; + allowedUsernames?: string | null; accessTokenTTL?: number; accessTokenMaxTTL?: number; accessTokenNumUsesLimit?: number; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx index fd77d1bea..b03576eeb 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx @@ -29,8 +29,15 @@ import { IdentityFormTab } from "./types"; const schema = z .object({ - tenancyOcid: z.string().trim().min(1, "Tenancy OCID is required."), - allowedUsernames: z.string(), + tenancyOcid: z + .string() + .trim() + .min(1, "Tenancy OCID cannot be empty.") + .refine( + (val) => /^ocid1\.tenancy\.oc1\..+$/.test(val), + "Invalid Tenancy OCID format. Must start with ocid1.tenancy.oc1." + ), + allowedUsernames: z.string().optional(), accessTokenTTL: z .string() .refine( @@ -110,7 +117,7 @@ export const IdentityOciAuthForm = ({ if (data) { reset({ tenancyOcid: data.tenancyOcid, - allowedUsernames: data.allowedUsernames, + allowedUsernames: data.allowedUsernames || undefined, accessTokenTTL: String(data.accessTokenTTL), accessTokenMaxTTL: String(data.accessTokenMaxTTL), accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit), @@ -125,7 +132,7 @@ export const IdentityOciAuthForm = ({ } else { reset({ tenancyOcid: "", - allowedUsernames: "", + allowedUsernames: undefined, accessTokenTTL: "2592000", accessTokenMaxTTL: "2592000", accessTokenNumUsesLimit: "0", @@ -161,7 +168,7 @@ export const IdentityOciAuthForm = ({ organizationId: orgId, identityId, tenancyOcid, - allowedUsernames: allowedUsernames || "", + allowedUsernames: allowedUsernames || undefined, accessTokenTTL: Number(accessTokenTTL), accessTokenMaxTTL: Number(accessTokenMaxTTL), accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), diff --git a/frontend/src/pages/organization/SettingsPage/route.tsx b/frontend/src/pages/organization/SettingsPage/route.tsx index e7fdb706a..3f7beb4e8 100644 --- a/frontend/src/pages/organization/SettingsPage/route.tsx +++ b/frontend/src/pages/organization/SettingsPage/route.tsx @@ -1,37 +1,33 @@ -import { faHome } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { - createFileRoute, - linkOptions, - stripSearchParams, -} from '@tanstack/react-router' -import { zodValidator } from '@tanstack/zod-adapter' -import { z } from 'zod' +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; -import { SettingsPage } from './SettingsPage' +import { SettingsPage } from "./SettingsPage"; const SettingsPageQueryParams = z.object({ - selectedTab: z.string().catch(''), -}) + selectedTab: z.string().catch("") +}); export const Route = createFileRoute( - '/_authenticate/_inject-org-details/_org-layout/organization/settings/', + "/_authenticate/_inject-org-details/_org-layout/organization/settings/" )({ component: SettingsPage, validateSearch: zodValidator(SettingsPageQueryParams), search: { - middlewares: [stripSearchParams({ selectedTab: '' })], + middlewares: [stripSearchParams({ selectedTab: "" })] }, context: () => ({ breadcrumbs: [ { - label: 'Home', + label: "Home", icon: () => , - link: linkOptions({ to: '/' }), + link: linkOptions({ to: "/" }) }, { - label: 'Settings', - }, - ], - }), -}) + label: "Settings" + } + ] + }) +});