diff --git a/backend/src/ee/controllers/v1/ssoController.ts b/backend/src/ee/controllers/v1/ssoController.ts index 4837dfd15..31ad59894 100644 --- a/backend/src/ee/controllers/v1/ssoController.ts +++ b/backend/src/ee/controllers/v1/ssoController.ts @@ -57,7 +57,6 @@ export const updateSSOConfig = async (req: Request, res: Response) => { entryPoint, issuer, cert, - audience } = req.body; const plan = await EELicenseService.getPlan(organizationId); @@ -78,9 +77,6 @@ export const updateSSOConfig = async (req: Request, res: Response) => { encryptedCert?: string; certIV?: string; certTag?: string; - encryptedAudience?: string; - audienceIV?: string; - audienceTag?: string; } const update: PatchUpdate = {}; @@ -132,18 +128,6 @@ export const updateSSOConfig = async (req: Request, res: Response) => { update.certIV = certIV; update.certTag = certTag; } - - if (audience) { - const { - ciphertext: encryptedAudience, - iv: audienceIV, - tag: audienceTag - } = client.encryptSymmetric(audience, key); - - update.encryptedAudience = encryptedAudience; - update.audienceIV = audienceIV; - update.audienceTag = audienceTag; - } const ssoConfig = await SSOConfig.findOneAndUpdate( { @@ -207,8 +191,7 @@ export const createSSOConfig = async (req: Request, res: Response) => { isActive, entryPoint, issuer, - cert, - audience + cert } = req.body; const plan = await EELicenseService.getPlan(organizationId); @@ -238,12 +221,6 @@ export const createSSOConfig = async (req: Request, res: Response) => { iv: certIV, tag: certTag } = client.encryptSymmetric(cert, key); - - const { - ciphertext: encryptedAudience, - iv: audienceIV, - tag: audienceTag - } = client.encryptSymmetric(audience, key); const ssoConfig = await new SSOConfig({ organization: new Types.ObjectId(organizationId), @@ -257,10 +234,7 @@ export const createSSOConfig = async (req: Request, res: Response) => { issuerTag, encryptedCert, certIV, - certTag, - encryptedAudience, - audienceIV, - audienceTag + certTag }).save(); return res.status(200).send(ssoConfig); diff --git a/backend/src/ee/helpers/organizations.ts b/backend/src/ee/helpers/organizations.ts index 477b05ccd..f9f125b39 100644 --- a/backend/src/ee/helpers/organizations.ts +++ b/backend/src/ee/helpers/organizations.ts @@ -51,13 +51,6 @@ export const getSSOConfigHelper = async ({ ssoConfig.certIV, ssoConfig.certTag ); - - const audience = client.decryptSymmetric( - ssoConfig.encryptedAudience, - key, - ssoConfig.audienceIV, - ssoConfig.audienceTag - ); return ({ _id: ssoConfig._id, @@ -66,7 +59,6 @@ export const getSSOConfigHelper = async ({ isActive: ssoConfig.isActive, entryPoint, issuer, - cert, - audience + cert }); } \ No newline at end of file diff --git a/backend/src/ee/models/ssoConfig.ts b/backend/src/ee/models/ssoConfig.ts index ab870afd7..9bb9c22c9 100644 --- a/backend/src/ee/models/ssoConfig.ts +++ b/backend/src/ee/models/ssoConfig.ts @@ -1,8 +1,12 @@ import { Schema, Types, model } from "mongoose"; +export enum AuthProvider { + OKTA_SAML = "okta-saml" +} + export interface ISSOConfig { organization: Types.ObjectId; - authProvider: "okta-saml" + authProvider: AuthProvider; isActive: boolean; encryptedEntryPoint: string; entryPointIV: string; @@ -13,9 +17,6 @@ export interface ISSOConfig { encryptedCert: string; certIV: string; certTag: string; - encryptedAudience: string; - audienceIV: string; - audienceTag: string; } const ssoConfigSchema = new Schema( @@ -26,9 +27,7 @@ const ssoConfigSchema = new Schema( }, authProvider: { type: String, - enum: [ - "okta-saml" - ], + enum: AuthProvider, required: true }, isActive: { @@ -61,15 +60,6 @@ const ssoConfigSchema = new Schema( }, certTag: { type: String - }, - encryptedAudience: { - type: String - }, - audienceIV: { - type: String - }, - audienceTag: { - type: String } }, { diff --git a/backend/src/ee/routes/v1/sso.ts b/backend/src/ee/routes/v1/sso.ts index 4771b5144..b54aa5942 100644 --- a/backend/src/ee/routes/v1/sso.ts +++ b/backend/src/ee/routes/v1/sso.ts @@ -1,6 +1,9 @@ import express from "express"; const router = express.Router(); import passport from "passport"; +import { + AuthProvider +} from "../../models"; import { requireAuth, requireOrganizationAuth, @@ -87,12 +90,11 @@ router.post( locationOrganizationId: "body" }), body("organizationId").exists().trim(), - body("authProvider").exists().isString(), + body("authProvider").exists().isString().isIn([AuthProvider.OKTA_SAML]), body("isActive").exists().isBoolean(), body("entryPoint").exists().isString(), body("issuer").exists().isString(), body("cert").exists().isString(), - body("audience").exists().isString(), validateRequest, ssoController.createSSOConfig ); @@ -113,7 +115,6 @@ router.patch( body("entryPoint").optional().isString(), body("issuer").optional().isString(), body("cert").optional().isString(), - body("audience").optional().isString(), validateRequest, ssoController.updateSSOConfig ); diff --git a/backend/src/ee/services/EELicenseService.ts b/backend/src/ee/services/EELicenseService.ts index 12e3e496a..13eca565c 100644 --- a/backend/src/ee/services/EELicenseService.ts +++ b/backend/src/ee/services/EELicenseService.ts @@ -66,7 +66,7 @@ class EELicenseService { customRateLimits: true, customAlerts: true, auditLogs: false, - samlSSO: false, + samlSSO: true, status: null, trial_end: null, has_used_trial: true diff --git a/backend/src/utils/auth.ts b/backend/src/utils/auth.ts index 772516d13..4ca60a622 100644 --- a/backend/src/utils/auth.ts +++ b/backend/src/utils/auth.ts @@ -147,7 +147,7 @@ const initializePassport = async () => { entryPoint: ssoConfig.entryPoint, issuer: ssoConfig.issuer, cert: ssoConfig.cert, - audience: ssoConfig.audience + audience: await getSiteURL() }); req.ssoConfig = ssoConfig; diff --git a/docs/documentation/platform/sso/azure.mdx b/docs/documentation/platform/sso/azure.mdx index 9d37f1a6b..f51047115 100644 --- a/docs/documentation/platform/sso/azure.mdx +++ b/docs/documentation/platform/sso/azure.mdx @@ -3,7 +3,10 @@ title: "Azure SAML" description: "Configure Azure SAML for Infisical SSO" --- -1. In the Azure Portal, navigate to the Azure Active Directory and select Enterprise applications. On this screen, select +1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**. +Next, copy the **ACS URL** and **Entity ID** to use when configuring the Okta SAML 2.0 application. + +2. In the Azure Portal, navigate to the Azure Active Directory and select Enterprise applications. On this screen, select the **+ New application** button. TODO: insert image. diff --git a/docs/documentation/platform/sso/okta.mdx b/docs/documentation/platform/sso/okta.mdx index 452473dfe..ce1e76ff0 100644 --- a/docs/documentation/platform/sso/okta.mdx +++ b/docs/documentation/platform/sso/okta.mdx @@ -1,72 +1,77 @@ --- title: "Okta SAML" -description: "Configure Okta SAML for Infisical SSO" +description: "Configure Okta SAML 2.0 for Infisical SSO" --- -1. In the Okta Admin Portal, select Applications > Applications from the - navigation. On the Applications screen, select the Create App Integration +Prerequisites: +- Okta Developer Account with access to create custom application integrations. + + +1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**. +Next, copy the **ACS URL** and **Entity ID** to use when configuring the Okta SAML 2.0 application. + +![SAML Okta Infisical initial configuration](../../../images/sso-okta-0.png) + +2. In the Okta Admin Portal, select Applications > Applications from the + navigation. On the Applications screen, select the **Create App Integration** button. ![SAML Okta create app integration](../../../images/sso-okta-1.png) -2. In the Create a New Application Integration dialog, select the SAML 2.0 radio button: +3. In the Create a New Application Integration dialog, select the **SAML 2.0** radio button: ![SAML Okta create SAML 2.0 integration](../../../images/sso-okta-2.png) -3. On the General Settings screen, give the application a unique, Infisical-specific name and select Next. +4. On the General Settings screen, give the application a unique name like Infisical and select **Next**. -4. On the Configure SAML screen, configure the following fields: +![SAML Okta create SAML 2.0 integration](../../../images/sso-okta-3.png) -- Single sign on URL: `https://app.infisical.com/api/v1/sso/saml2/:identifier`; we'll update the `:identifier` part later in step 6. -- Audience URI (SP Entity ID): `https://app.infisical.com` +5. On the Configure SAML screen, set the **Single sign-on URL** to **ACS URL** and **Audience URI (SP Entity ID)** to +**Entity ID** from step 1. -![SAML Okta configure IdP fields](../../../images/sso-okta-3.png) +![SAML Okta configure IdP fields](../../../images/sso-okta-4.png) If you're self-hosting Infisical, then you will want to replace `https://app.infisical.com` with your own domain. -4. Also on the Configure SAML screen, configure the Attribute Statements to map: +6. Also on the Configure SAML screen, configure the **Attribute Statements** to map: - `id -> user.id`, - `email -> user.email`, - `firstName -> user.firstName` - `lastName -> user.lastName` -![SAML Okta attribute statements](../../../images/sso-okta-4.png) +![SAML Okta attribute statements](../../../images/sso-okta-5.png) -Once configured, select the Next button to proceed to the Feedback screen and select Finish. +Once configured, select the **Next** button to proceed to the Feedback screen and select **Finish**. -5. Get IdP values +7. Get IdP values -Once your application is created, select the Sign On tab for the app and select the View Setup Instructions button located on the right side of the screen: +Once your application is created, select the **Sign On** tab for the app and select the **View Setup Instructions** button located on the right side of the screen: -Copy the Identity Provider Single Sign-On URL, the Identity Provider Issuer, and the X.509 Certificate to be pasted into your Infisical SAML SSO configuration details with the following map: +![SAML Okta view setup instructions](../../../images/sso-okta-6.png) -- `Audience -> Audience URI (SP Entity ID) from Okta` -- `Entrypoint -> Identity Provider Single Sign-On URL from Okta` -- `Issuer -> Identity Provider Issuer from Okta` -- `Certificate -> X.509 Certificate from Okta` +Copy the **Identity Provider Single Sign-On URL**, the **Identity Provider Issuer**, and the **X.509 Certificate** to use when finishing configuring the Okta SAML in Infisical. -![SAML Okta IdP values](../../../images/saml-okta-5.png) +![SAML Okta IdP values](../../../images/sso-okta-7.png) -![SAML Okta paste values into Infisical](../../../images/sso-okta-6.png) +Back in Infisical, set **Entrypoint** to **Identity Provider Single Sign-On URL**, **Issuer** to **Identity Provider Issuer**, +and **Certificate** to **X.509 Certificate** from above. Once you've done that, press **Add** to complete the required configuration. -6. Create the SSO configuration and copy your SSO identifier in Infisical; update `:identifier` from step 4 earlier to be this value. +![SAML Okta paste values into Infisical](../../../images/sso-okta-8.png) -![SAML Okta assignments](../../../images/sso-okta-7.png) - -7. Assignments - -Finally, navigate to the Assignments tab and select the Assign button: +8. Finally, navigate to the **Assignments** tab and select **Assign** You can assign access to the application on a user-by-user basis using the Assign to People option, or in-bulk using the Assign to Groups option. -![SAML Okta assignment](../../../images/sso-okta-8.png) +![SAML Okta assignment](../../../images/sso-okta-9.png) At this point, you have configured everything you need within the context of the Okta Admin Portal. -8. Return to Infisical and enable SAML SSO. +9. Return to Infisical and enable SAML SSO. Enabling SAML SSO enforces all members in your organization to only be able to log into Infisical via Okta. + +![SAML Okta assignment](../../../images/sso-okta-10.png) \ No newline at end of file diff --git a/docs/documentation/platform/sso/overview.mdx b/docs/documentation/platform/sso/overview.mdx index 67a53007d..38aae128c 100644 --- a/docs/documentation/platform/sso/overview.mdx +++ b/docs/documentation/platform/sso/overview.mdx @@ -13,11 +13,4 @@ description: "Log in to Infisical via SSO protocols" You can configure your organization in Infisical to have members authenticate with the platform via protocols like [SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0). To note, configuring SSO retains the end-to-end encrypted architecture of Infisical because we decouple the **authentication** and **decryption** steps. In all login with SSO implementations, -your IdP cannot and will not have access to the decryption key needed to decrypt your secrets. - -## Configuration - -Head over to your organization Settings > Authentication > SAML SSO Configuration. - -Next, press "Set up SAML SSO" in the SAML SSO and follow the instructions -below to configure SSO for your identity provider. +your IdP cannot and will not have access to the decryption key needed to decrypt your secrets. \ No newline at end of file diff --git a/docs/images/sso-okta-0.png b/docs/images/sso-okta-0.png new file mode 100644 index 000000000..9de80c695 Binary files /dev/null and b/docs/images/sso-okta-0.png differ diff --git a/docs/images/sso-okta-10.png b/docs/images/sso-okta-10.png new file mode 100644 index 000000000..2bd4a84e9 Binary files /dev/null and b/docs/images/sso-okta-10.png differ diff --git a/docs/images/sso-okta-3.png b/docs/images/sso-okta-3.png index eccc8d277..245f48df0 100644 Binary files a/docs/images/sso-okta-3.png and b/docs/images/sso-okta-3.png differ diff --git a/docs/images/sso-okta-4.png b/docs/images/sso-okta-4.png index e3c413a6f..c4f977fb8 100644 Binary files a/docs/images/sso-okta-4.png and b/docs/images/sso-okta-4.png differ diff --git a/docs/images/sso-okta-5.png b/docs/images/sso-okta-5.png index 4acc846f9..e3c413a6f 100644 Binary files a/docs/images/sso-okta-5.png and b/docs/images/sso-okta-5.png differ diff --git a/docs/images/sso-okta-6.png b/docs/images/sso-okta-6.png index 82300afdb..59b4da358 100644 Binary files a/docs/images/sso-okta-6.png and b/docs/images/sso-okta-6.png differ diff --git a/docs/images/sso-okta-7.png b/docs/images/sso-okta-7.png index 2bd4a84e9..4acc846f9 100644 Binary files a/docs/images/sso-okta-7.png and b/docs/images/sso-okta-7.png differ diff --git a/docs/images/sso-okta-8.png b/docs/images/sso-okta-8.png index bf9f98301..82300afdb 100644 Binary files a/docs/images/sso-okta-8.png and b/docs/images/sso-okta-8.png differ diff --git a/docs/images/sso-okta-9.png b/docs/images/sso-okta-9.png new file mode 100644 index 000000000..bf9f98301 Binary files /dev/null and b/docs/images/sso-okta-9.png differ diff --git a/frontend/src/config/request.ts b/frontend/src/config/request.ts index 2e91be462..8f8669010 100644 --- a/frontend/src/config/request.ts +++ b/frontend/src/config/request.ts @@ -4,7 +4,8 @@ import SecurityClient from "@app/components/utilities/SecurityClient"; import { getAuthToken, getMfaTempToken, - getSignupTempToken} from "@app/reactQuery"; + getSignupTempToken +} from "@app/reactQuery"; export const apiRequest = axios.create({ baseURL: "/", diff --git a/frontend/src/hooks/api/ssoConfig/queries.tsx b/frontend/src/hooks/api/ssoConfig/queries.tsx index c17c2531a..f3451060f 100644 --- a/frontend/src/hooks/api/ssoConfig/queries.tsx +++ b/frontend/src/hooks/api/ssoConfig/queries.tsx @@ -29,8 +29,7 @@ export const useCreateSSOConfig = () => { isActive, entryPoint, issuer, - cert, - audience + cert }: { organizationId: string; authProvider: string; @@ -38,7 +37,6 @@ export const useCreateSSOConfig = () => { entryPoint: string; issuer: string; cert: string; - audience: string; }) => { const { data } = await apiRequest.post( "/api/v1/sso/config", @@ -48,8 +46,7 @@ export const useCreateSSOConfig = () => { isActive, entryPoint, issuer, - cert, - audience + cert } ); @@ -70,8 +67,7 @@ export const useUpdateSSOConfig = () => { isActive, entryPoint, issuer, - cert, - audience + cert }: { organizationId: string; authProvider?: string; @@ -79,7 +75,6 @@ export const useUpdateSSOConfig = () => { entryPoint?: string; issuer?: string; cert?: string; - audience?: string; }) => { const { data } = await apiRequest.patch( "/api/v1/sso/config", @@ -89,8 +84,7 @@ export const useUpdateSSOConfig = () => { ...(isActive !== undefined ? { isActive } : {}), ...(entryPoint !== undefined ? { entryPoint } : {}), ...(issuer !== undefined ? { issuer } : {}), - ...(cert !== undefined ? { cert } : {}), - ...(audience !== undefined ? { audience } : {}) + ...(cert !== undefined ? { cert } : {}) } ); diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx index 73213db31..7f3fba892 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgSSOSection.tsx @@ -6,6 +6,7 @@ import { Button, Switch, UpgradePlanModal } from "@app/components/v2"; import { useOrganization, useSubscription } from "@app/context"; import { useGetSSOConfig, + useCreateSSOConfig, useUpdateSSOConfig } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -27,6 +28,8 @@ export const OrgSSOSection = (): JSX.Element => { "addSSO" ] as const); + const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateSSOConfig(); + const handleSamlSSOToggle = async (value: boolean) => { try { if (!currentOrg?._id) return; @@ -49,6 +52,33 @@ export const OrgSSOSection = (): JSX.Element => { } } + const addSSOBtnClick = async () => { + try { + if (subscription?.samlSSO && currentOrg) { + if (!data) { + // case: SAML SSO is not configured + // -> initialize empty SAML SSO configuration + await createMutateAsync({ + organizationId: currentOrg._id, + authProvider: "okta-saml", + isActive: false, + entryPoint: "", + issuer: "", + cert: "" + }); + } + + handlePopUpOpen("addSSO"); + } else { + handlePopUpOpen("upgradePlan"); + } + } catch (err) { + console.error(err); + } + } + + console.log("getSSOConfig: ", data); + return (
@@ -57,13 +87,7 @@ export const OrgSSOSection = (): JSX.Element => { {!isLoading && ( )}
- {!isLoading && data && ( - <> -
- handleSamlSSOToggle(value)} - isChecked={data.isActive} - > - Enable SAML SSO - -
-
-

SSO identifier

-

{data._id}

-
-
-

Type

-

{ssoAuthProviderMap[data.authProvider]}

-
-
-

Audience

-

{data.audience}

-
-
-

Entrypoint

-

{data.entryPoint}

-
-
-

Issuer

-

{data.issuer}

-
- + {data && ( +
+ handleSamlSSOToggle(value)} + isChecked={data ? data.isActive : false} + > + Enable SAML SSO + +
)} +
+

SSO identifier

+

{(data && data._id !== "") ? data._id : "-"}

+
+
+

Type

+

{(data && data.authProvider !== "") ? ssoAuthProviderMap[data.authProvider] : "-"}

+
+
+

Entrypoint

+

{(data && data.entryPoint !== "") ? data.entryPoint : "-"}

+
+
+

Issuer

+

{(data && data.issuer !== "") ? data.issuer : "-"}

+
; @@ -70,8 +68,7 @@ export const SSOModal = ({ authProvider: data?.authProvider ?? "", entryPoint: data?.entryPoint ?? "", issuer: data?.issuer ?? "", - cert: data?.cert ?? "", - audience: data?.audience ?? "" + cert: data?.cert ?? "" }); } }, [data]); @@ -80,8 +77,7 @@ export const SSOModal = ({ authProvider, entryPoint, issuer, - cert, - audience + cert }: AddSSOFormData) => { try { if (!currentOrg) return; @@ -93,8 +89,7 @@ export const SSOModal = ({ isActive: false, entryPoint, issuer, - cert, - audience + cert }); } else { await updateMutateAsync({ @@ -103,8 +98,7 @@ export const SSOModal = ({ isActive: false, entryPoint, issuer, - cert, - audience + cert }); } @@ -160,24 +154,16 @@ export const SSOModal = ({ )} /> - {authProvider && authProvider === "okta-saml" && ( + {authProvider && authProvider === "okta-saml" && data && ( <> - ( - - - - )} - /> +
+

ACS URL

+

{`${window.origin}/api/v1/sso/saml2/${data._id}`}

+
+
+

Entity ID

+

{window.origin}

+