diff --git a/backend/src/services/app-connection/railway/railway-connection-public-client.ts b/backend/src/services/app-connection/railway/railway-connection-public-client.ts index 1c8bd9cc2..47eb3f396 100644 --- a/backend/src/services/app-connection/railway/railway-connection-public-client.ts +++ b/backend/src/services/app-connection/railway/railway-connection-public-client.ts @@ -75,7 +75,7 @@ class RailwayPublicClient { async send( query: string, options: RailwaySendReqOptions, - variables: Record> = {}, + variables: Record = {}, retryAttempt: number = 0 ): Promise { const body = { @@ -117,6 +117,25 @@ class RailwayPublicClient { } } + async getDeployments( + config: RailwaySendReqOptions, + variables: { input: { serviceId: string; environmentId: string }; first?: number } + ) { + return this.send>( + `query deployments($input: DeploymentListInput!, $first: Int) { deployments(first: $first, input: $input) { edges { node { id } } } }`, + config, + variables + ); + } + + async redeployDeployment(config: RailwaySendReqOptions, variables: { input: { deploymentId: string } }) { + return this.send>( + `mutation deploymentRedeploy($deploymentId: String!) { deploymentRedeploy(id: $deploymentId) { id } }`, + config, + { deploymentId: variables.input.deploymentId } + ); + } + async getSubscriptionType(config: RailwaySendReqOptions & { projectId: string }) { const res = await this.send( `query project($projectId: String!) { project(id: $projectId) { subscriptionType }}`, @@ -213,7 +232,9 @@ class RailwayPublicClient { async deleteVariable( config: RailwaySendReqOptions, - variables: { input: { projectId: string; environmentId: string; name: string; serviceId?: string } } + variables: { + input: { projectId: string; environmentId: string; name: string; skipDeploys?: boolean; serviceId?: string }; + } ) { await this.send }>>( `mutation variableDelete($input: VariableDeleteInput!) { variableDelete(input: $input) }`, @@ -222,6 +243,26 @@ class RailwayPublicClient { ); } + async upsertCollection( + config: RailwaySendReqOptions, + variables: { + input: { + projectId: string; + environmentId: string; + variables: Record; + skipDeploys?: boolean; + serviceId?: string; + replace?: boolean; + }; + } + ) { + return this.send>( + `mutation variableCollectionUpsert($input: VariableCollectionUpsertInput!) { variableCollectionUpsert(input: $input) }`, + config, + variables + ); + } + async upsertVariable( config: RailwaySendReqOptions, variables: { input: { projectId: string; environmentId: string; name: string; value: string; serviceId?: string } } diff --git a/backend/src/services/secret-sync/railway/railway-sync-fns.ts b/backend/src/services/secret-sync/railway/railway-sync-fns.ts index 07862aeb5..731691acf 100644 --- a/backend/src/services/secret-sync/railway/railway-sync-fns.ts +++ b/backend/src/services/secret-sync/railway/railway-sync-fns.ts @@ -12,6 +12,8 @@ export const RailwaySyncFns = { async getSecrets(secretSync: TRailwaySyncWithCredentials): Promise { try { const config = secretSync.destinationConfig; + const { keySchema } = secretSync.syncOptions; + const { environment } = secretSync; const variables = await RailwayPublicAPI.getVariables(secretSync.connection, { projectId: config.projectId, @@ -26,6 +28,10 @@ export const RailwaySyncFns = { // eslint-disable-next-line no-continue if (key.startsWith("RAILWAY_")) continue; + // Check if key matches the schema + // eslint-disable-next-line no-continue + if (!matchesSchema(key, environment?.slug || "", keySchema)) continue; + entries[key] = { value }; @@ -40,60 +46,73 @@ export const RailwaySyncFns = { } }, + /** + * Syncs secrets to Railway and redeploys the service if needed. + * + * Gets existing Railway vars, merges with new secrets (keeping Railway vars if deletion is disabled), + * then replaces every variable with the new values, if variable is not in the secretMap, it is deleted. + * If there's a service, triggers a redeploy to pick up the changes. + */ async syncSecrets(secretSync: TRailwaySyncWithCredentials, secretMap: TSecretMap) { - const { - environment, - syncOptions: { disableSecretDeletion, keySchema } - } = secretSync; - const railwaySecrets = await this.getSecrets(secretSync); - const config = secretSync.destinationConfig; + try { + const { + syncOptions: { disableSecretDeletion } + } = secretSync; + const railwaySecrets = await this.getSecrets(secretSync); + const config = secretSync.destinationConfig; - for await (const key of Object.keys(secretMap)) { - try { - const existing = railwaySecrets[key]; + const railwaySecretsMap = Object.fromEntries( + Object.entries(railwaySecrets).map(([key, secret]) => [key, secret.value]) + ); + const secretMapMap = Object.fromEntries(Object.entries(secretMap).map(([key, secret]) => [key, secret.value])); - if (existing === undefined || existing.value !== secretMap[key].value) { - await RailwayPublicAPI.upsertVariable(secretSync.connection, { - input: { - projectId: config.projectId, - environmentId: config.environmentId, - serviceId: config.serviceId || undefined, - name: key, - value: secretMap[key].value ?? "" - } - }); + const toReplace = disableSecretDeletion ? { ...railwaySecretsMap, ...secretMapMap } : secretMapMap; + + const upserted = await RailwayPublicAPI.upsertCollection(secretSync.connection, { + input: { + projectId: config.projectId, + environmentId: config.environmentId, + serviceId: config.serviceId || undefined, + skipDeploys: true, + variables: toReplace, + replace: true } - } catch (error) { + }); + + if (!upserted) throw new SecretSyncError({ - error, - secretKey: key + message: "Failed to upsert secrets to Railway" }); - } - } - if (disableSecretDeletion) return; + if (!config.serviceId) return; - for await (const key of Object.keys(railwaySecrets)) { - try { - // eslint-disable-next-line no-continue - if (!matchesSchema(key, environment?.slug || "", keySchema)) continue; + const latestDeployment = await RailwayPublicAPI.getDeployments(secretSync.connection, { + input: { + serviceId: config.serviceId, + environmentId: config.environmentId + }, + first: 1 + }); - if (!secretMap[key]) { - await RailwayPublicAPI.deleteVariable(secretSync.connection, { - input: { - projectId: config.projectId, - environmentId: config.environmentId, - serviceId: config.serviceId || undefined, - name: key - } - }); + const latestDeploymentId = latestDeployment?.deployments.edges[0].node.id; + + if (!latestDeploymentId) + throw new SecretSyncError({ + message: "Failed to get latest deployment from Railway" + }); + + await RailwayPublicAPI.redeployDeployment(secretSync.connection, { + input: { + deploymentId: latestDeploymentId } - } catch (error) { - throw new SecretSyncError({ - error, - secretKey: key - }); - } + }); + } catch (error) { + if (error instanceof SecretSyncError) throw error; + + throw new SecretSyncError({ + error, + message: "Failed to sync secrets to Railway" + }); } }, @@ -101,24 +120,37 @@ export const RailwaySyncFns = { const existing = await this.getSecrets(secretSync); const config = secretSync.destinationConfig; - for await (const secret of Object.keys(existing)) { - try { - if (secret in secretMap) { - await RailwayPublicAPI.deleteVariable(secretSync.connection, { - input: { - projectId: config.projectId, - environmentId: config.environmentId, - serviceId: config.serviceId || undefined, - name: secret - } - }); + // Create a new variables object excluding secrets that exist in secretMap + const remainingVariables = Object.fromEntries( + Object.entries(existing) + .filter(([key]) => !(key in secretMap)) + .map(([key, secret]) => [key, secret.value]) + ); + + try { + const upserted = await RailwayPublicAPI.upsertCollection(secretSync.connection, { + input: { + projectId: config.projectId, + environmentId: config.environmentId, + serviceId: config.serviceId || undefined, + skipDeploys: true, + variables: remainingVariables, + replace: true } - } catch (error) { + }); + + if (!upserted) { throw new SecretSyncError({ - error, - secretKey: secret + message: "Failed to remove secrets from Railway" }); } + } catch (error) { + if (error instanceof SecretSyncError) throw error; + + throw new SecretSyncError({ + error, + message: "Failed to remove secrets from Railway" + }); } } }; diff --git a/docs/docs.json b/docs/docs.json index eb69cd2b8..b342bf09e 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -221,6 +221,7 @@ ] }, "documentation/platform/sso/auth0-oidc", + "documentation/platform/sso/pingone-oidc", { "group": "General OIDC", "pages": [ diff --git a/docs/documentation/platform/gateways/gateway-deployment.mdx b/docs/documentation/platform/gateways/gateway-deployment.mdx index 40b98965a..cef258c41 100644 --- a/docs/documentation/platform/gateways/gateway-deployment.mdx +++ b/docs/documentation/platform/gateways/gateway-deployment.mdx @@ -95,7 +95,7 @@ To successfully deploy an Infisical Gateway for use, follow these steps in order Ensure a relay server is running and accessible before you deploy any gateways. You have two options: - **Managed relay (Infisical Cloud, US/EU only):** Managed relays are only available for Infisical Cloud instances in the US and EU regions. If you are using Infisical Cloud in these regions, you can use the provided managed relay. - - **Self-hosted relay:** For all other cases, including all self-hosted and dedicated enterprise instances of Infisical, you must deploy your own relay server. You can also choose to deploy your own relay server when using Infisical Cloud if you require reduced geographic proximity to your target resources for lower latency or to reduce network congestion. For setup instructions, see the Relay Deployment Guide. + - **Self-hosted relay:** For all other cases, including all self-hosted and dedicated enterprise instances of Infisical, you must deploy your own relay server. You can also choose to deploy your own relay server when using Infisical Cloud if you require reduced geographic proximity to your target resources for lower latency or to reduce network congestion. For setup instructions, see the [Relay Deployment Guide](/documentation/platform/gateways/relay-deployment). Make sure the Infisical CLI is installed on the machine or environment where you plan to deploy the gateway. The CLI is required for gateway installation and management. diff --git a/docs/documentation/platform/sso/auth0-oidc.mdx b/docs/documentation/platform/sso/auth0-oidc.mdx index 4b54c053d..0f616c519 100644 --- a/docs/documentation/platform/sso/auth0-oidc.mdx +++ b/docs/documentation/platform/sso/auth0-oidc.mdx @@ -6,7 +6,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO." Auth0 OIDC SSO is a paid feature. If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical, then you - should contact sales@infisical.com to purchase an enterprise license to use + should contact sales@infisical.com to purchase a self-hosted license to use it. @@ -55,7 +55,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO." Enabling OIDC allows members in your organization to log into Infisical via Auth0. - ![OIDC auth0 enable OIDC](../../../images/sso/auth0-oidc/enable-oidc.png) + ![OIDC auth0 enable OIDC](../../../images/sso/enable-oidc.png) diff --git a/docs/documentation/platform/sso/general-oidc/overview.mdx b/docs/documentation/platform/sso/general-oidc/overview.mdx index 07ddaaedd..586f66f26 100644 --- a/docs/documentation/platform/sso/general-oidc/overview.mdx +++ b/docs/documentation/platform/sso/general-oidc/overview.mdx @@ -7,7 +7,7 @@ description: "Learn how to configure OIDC for Infisical SSO with any OIDC-compli OIDC SSO is a paid feature. If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical, then you - should contact sales@infisical.com to purchase an enterprise license to use + should contact sales@infisical.com to purchase a self-hosted license to use it. diff --git a/docs/documentation/platform/sso/keycloak-oidc/overview.mdx b/docs/documentation/platform/sso/keycloak-oidc/overview.mdx index 2c75fc6fe..727bf9be6 100644 --- a/docs/documentation/platform/sso/keycloak-oidc/overview.mdx +++ b/docs/documentation/platform/sso/keycloak-oidc/overview.mdx @@ -7,7 +7,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO." Keycloak OIDC SSO is a paid feature. If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical, then - you should contact sales@infisical.com to purchase an enterprise license to + you should contact sales@infisical.com to purchase a self-hosted license to use it. @@ -82,7 +82,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO." Enabling OIDC SSO allows members in your organization to log into Infisical via Keycloak. - ![OIDC keycloak enable OIDC](/images/sso/keycloak-oidc/enable-oidc.png) + ![OIDC keycloak enable OIDC](/images/sso/enable-oidc.png) diff --git a/docs/documentation/platform/sso/pingone-oidc.mdx b/docs/documentation/platform/sso/pingone-oidc.mdx new file mode 100644 index 000000000..1fea5f815 --- /dev/null +++ b/docs/documentation/platform/sso/pingone-oidc.mdx @@ -0,0 +1,108 @@ +--- +title: "PingOne OIDC" +description: "Learn how to configure PingOne OIDC for Infisical SSO." +--- + + + PingOne OIDC SSO is a paid feature. If you're using Infisical Cloud, then it is + available under the **Pro Tier**. If you're self-hosting Infisical, then you + should contact sales@infisical.com to purchase a self-hosted license to use + it. + + + + + 1.1. From the Application's Page, create a new OIDC Web App application. + ![OIDC pingone create application](../../../images/sso/pingone-oidc/pingone-create-application.png) + + 1.2. Enable the application by pressing the "Enable" toggle. + ![OIDC PingOne Enable Application](../../../images/sso/pingone-oidc/pingone-enable-application.png) + + + 1.3. In the Application "Configuration" tab, press the "Edit" pencil icon to configure the application callback URI. + ![OIDC PingOne Edit Application Configuration](../../../images/sso/pingone-oidc/pingone-edit-application-configuration.png) + + + 1.4 Set the Redirect URL to `https://app.infisical.com/api/v1/sso/oidc/callback` and press the "Save" button. + ![OIDC PingOne Edit Redirect URI](../../../images/sso/pingone-oidc/pingone-edit-application-redirect-uri.png) + + + + If you're self-hosting Infisical, then you will want to replace https://app.infisical.com with your own domain. + + + + 1.5 After configuring the redirect URL, go to the "Attribute Mappings" tab and press the "Edit" pencil icon to configure the attribute mappings. + ![OIDC PingOne Edit Attribute Mappings](../../../images/sso/pingone-oidc/pingone-edit-application-attribute-mappings.png) + + 1.6 Map the following attributes: + - `email` -> `Email Address` + - `name` -> `Username` + ![OIDC PingOne Edit Attribute Mappings](../../../images/sso/pingone-oidc/pingone-edit-application-attribute-mappings-2.png) + + Once done, press the "Save" button. + + + + 2.1. Open the "Overview" tab and copy the **Client ID** and **Client Secret**. + ![OIDC PingOne Application Credential](../../../images/sso/pingone-oidc/pingone-overview-credentials.png) + + 2.2. Still in the "Overview" tab, scroll down to the Connection Details section and retrieve the **OIDC Discovery Endpoint**. + ![OIDC PingOne OIDC Discovery Endpoint](../../../images/sso/pingone-oidc/pingone-overview-oidc-discovery-endpoint.png) + + Keep these values handy as we will need them in the next steps. + + + + 3.1. Back in Infisical, head to the **Single Sign-On (SSO)** page and select the **General** tab. Click **Connect** for **OIDC**. + ![OIDC SSO Connect](../../../images/sso/connect-oidc.png) + + 3.2. For configuration type, select **Discovery URL**. Then, set **Discovery Document URL**, **Client ID**, and **Client Secret** from step 2.1 and 2.2. + + ![OIDC PingOne paste values into Infisical](../../../images/sso/pingone-oidc/infisical-configure-oidc.png) + + + Currently, the following JWT signature algorithms are supported: RS256, RS512, HS256, and EdDSA + + + Once you've done that, press **Update** to complete the required configuration. + + + + Enabling OIDC allows members in your organization to log into Infisical via PingOne + + ![OIDC PingOne enable OIDC](../../../images/sso/enable-oidc.png) + + + + Enforcing OIDC SSO ensures that members in your organization can only access Infisical + by logging into the organization via PingOne. + + To enforce OIDC SSO, you're required to test out the OpenID connection by successfully authenticating at least one PingOne user with Infisical. + Once you've completed this requirement, you can toggle the **Enforce OIDC SSO** button to enforce OIDC SSO. + + + We recommend ensuring that your account is provisioned using the application in PingOne + prior to enforcing OIDC SSO to prevent any unintended issues. + + + In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin. + + + + + + If you are only using one organization on your Infisical instance, you can configure a default organization in the [Server Admin Console](../admin-panel/server-admin#default-organization) to expedite OIDC login. + + + + If you're configuring OIDC SSO on a self-hosted instance of Infisical, make + sure to set the `AUTH_SECRET` and `SITE_URL` environment variable for it to + work: +
+ - `AUTH_SECRET`: A secret key used for signing and verifying JWT. This + can be a random 32-byte base64 string generated with `openssl rand -base64 + 32`. +
+ - `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com) + diff --git a/docs/images/sso/auth0-oidc/enable-oidc.png b/docs/images/sso/enable-oidc.png similarity index 100% rename from docs/images/sso/auth0-oidc/enable-oidc.png rename to docs/images/sso/enable-oidc.png diff --git a/docs/images/sso/keycloak-oidc/enable-oidc.png b/docs/images/sso/keycloak-oidc/enable-oidc.png deleted file mode 100644 index 0a43f22ed..000000000 Binary files a/docs/images/sso/keycloak-oidc/enable-oidc.png and /dev/null differ diff --git a/docs/images/sso/pingone-oidc/infisical-configure-oidc.png b/docs/images/sso/pingone-oidc/infisical-configure-oidc.png new file mode 100644 index 000000000..b60ff0f27 Binary files /dev/null and b/docs/images/sso/pingone-oidc/infisical-configure-oidc.png differ diff --git a/docs/images/sso/pingone-oidc/pingone-create-application.png b/docs/images/sso/pingone-oidc/pingone-create-application.png new file mode 100644 index 000000000..7f188da7d Binary files /dev/null and b/docs/images/sso/pingone-oidc/pingone-create-application.png differ diff --git a/docs/images/sso/pingone-oidc/pingone-edit-application-attribute-mappings-2.png b/docs/images/sso/pingone-oidc/pingone-edit-application-attribute-mappings-2.png new file mode 100644 index 000000000..05f212b73 Binary files /dev/null and b/docs/images/sso/pingone-oidc/pingone-edit-application-attribute-mappings-2.png differ diff --git a/docs/images/sso/pingone-oidc/pingone-edit-application-attribute-mappings.png b/docs/images/sso/pingone-oidc/pingone-edit-application-attribute-mappings.png new file mode 100644 index 000000000..1beba0117 Binary files /dev/null and b/docs/images/sso/pingone-oidc/pingone-edit-application-attribute-mappings.png differ diff --git a/docs/images/sso/pingone-oidc/pingone-edit-application-configuration.png b/docs/images/sso/pingone-oidc/pingone-edit-application-configuration.png new file mode 100644 index 000000000..9be619298 Binary files /dev/null and b/docs/images/sso/pingone-oidc/pingone-edit-application-configuration.png differ diff --git a/docs/images/sso/pingone-oidc/pingone-edit-application-redirect-uri.png b/docs/images/sso/pingone-oidc/pingone-edit-application-redirect-uri.png new file mode 100644 index 000000000..e5292a85f Binary files /dev/null and b/docs/images/sso/pingone-oidc/pingone-edit-application-redirect-uri.png differ diff --git a/docs/images/sso/pingone-oidc/pingone-enable-application.png b/docs/images/sso/pingone-oidc/pingone-enable-application.png new file mode 100644 index 000000000..51219b9fd Binary files /dev/null and b/docs/images/sso/pingone-oidc/pingone-enable-application.png differ diff --git a/docs/images/sso/pingone-oidc/pingone-overview-credentials.png b/docs/images/sso/pingone-oidc/pingone-overview-credentials.png new file mode 100644 index 000000000..af609ff0b Binary files /dev/null and b/docs/images/sso/pingone-oidc/pingone-overview-credentials.png differ diff --git a/docs/images/sso/pingone-oidc/pingone-overview-oidc-discovery-endpoint.png b/docs/images/sso/pingone-oidc/pingone-overview-oidc-discovery-endpoint.png new file mode 100644 index 000000000..da7e981b6 Binary files /dev/null and b/docs/images/sso/pingone-oidc/pingone-overview-oidc-discovery-endpoint.png differ diff --git a/frontend/public/images/integrations/IBM.png b/frontend/public/images/integrations/IBM.png new file mode 100644 index 000000000..7fafbb6c3 Binary files /dev/null and b/frontend/public/images/integrations/IBM.png differ diff --git a/frontend/src/components/auth/EnterEmailStep.tsx b/frontend/src/components/auth/EnterEmailStep.tsx index a74e9611c..8bf903c25 100644 --- a/frontend/src/components/auth/EnterEmailStep.tsx +++ b/frontend/src/components/auth/EnterEmailStep.tsx @@ -1,9 +1,8 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link } from "@tanstack/react-router"; -import axios from "axios"; +import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { useSendVerificationEmail } from "@app/hooks/api"; import { Button, Input } from "../v2"; @@ -35,11 +34,10 @@ export default function EnterEmailStep({ * Verifies if the entered email "looks" correct */ const emailCheck = async () => { + const isValid = z.string().email().safeParse(email); + let emailCheckBool = false; - if (!email) { - setEmailError(true); - emailCheckBool = true; - } else if (!email.includes("@") || !email.includes(".") || !/[a-z]/.test(email)) { + if (!isValid.success) { setEmailError(true); emailCheckBool = true; } else { @@ -48,19 +46,9 @@ export default function EnterEmailStep({ // If everything is correct, go to the next step if (!emailCheckBool) { - try { - await mutateAsync({ email: email.toLowerCase() }); - setEmail(email.toLowerCase()); - incrementStep(); - } catch (e) { - if (axios.isAxiosError(e)) { - const { message = "Something went wrong" } = e.response?.data as { message: string }; - createNotification({ - type: "error", - text: message - }); - } - } + await mutateAsync({ email: email.toLowerCase() }); + setEmail(email.toLowerCase()); + incrementStep(); } }; diff --git a/frontend/src/helpers/auditLogStreams.ts b/frontend/src/helpers/auditLogStreams.ts index faa132dd2..ab216957c 100644 --- a/frontend/src/helpers/auditLogStreams.ts +++ b/frontend/src/helpers/auditLogStreams.ts @@ -12,7 +12,8 @@ export const AUDIT_LOG_STREAM_PROVIDER_MAP: Record< [LogProvider.Cribl]: { name: "Cribl", image: "Cribl.png", size: 60 }, [LogProvider.Custom]: { name: "Custom", icon: faCode }, [LogProvider.Datadog]: { name: "Datadog", image: "Datadog.png" }, - [LogProvider.Splunk]: { name: "Splunk", image: "Splunk.png", size: 65 } + [LogProvider.Splunk]: { name: "Splunk", image: "Splunk.png", size: 65 }, + [LogProvider.QRadar]: { name: "IBM QRadar", image: "IBM.png" } }; // Strictly for showing to the client in the front-end diff --git a/frontend/src/hooks/api/auditLogStreams/enums.ts b/frontend/src/hooks/api/auditLogStreams/enums.ts index ebef18574..71d11500a 100644 --- a/frontend/src/hooks/api/auditLogStreams/enums.ts +++ b/frontend/src/hooks/api/auditLogStreams/enums.ts @@ -3,5 +3,6 @@ export enum LogProvider { Cribl = "cribl", Custom = "custom", Datadog = "datadog", - Splunk = "splunk" + Splunk = "splunk", + QRadar = "qradar" } diff --git a/frontend/src/hooks/api/auditLogStreams/types/index.ts b/frontend/src/hooks/api/auditLogStreams/types/index.ts index f780510c2..6efd5a77d 100644 --- a/frontend/src/hooks/api/auditLogStreams/types/index.ts +++ b/frontend/src/hooks/api/auditLogStreams/types/index.ts @@ -3,6 +3,7 @@ import { TAzureProviderLogStream } from "./providers/azure-provider"; import { TCriblProviderLogStream } from "./providers/cribl-provider"; import { TCustomProviderLogStream } from "./providers/custom-provider"; import { TDatadogProviderLogStream } from "./providers/datadog-provider"; +import { TQRadarProviderLogStream } from "./providers/qradar-provider"; import { TSplunkProviderLogStream } from "./providers/splunk-provider"; export type TAuditLogStream = @@ -18,6 +19,7 @@ export type TAuditLogStreamProviderMap = { [LogProvider.Custom]: TCustomProviderLogStream; [LogProvider.Datadog]: TDatadogProviderLogStream; [LogProvider.Splunk]: TSplunkProviderLogStream; + [LogProvider.QRadar]: TQRadarProviderLogStream; }; export type TCreateAuditLogStreamDTO = Pick; diff --git a/frontend/src/hooks/api/auditLogStreams/types/providers/qradar-provider.ts b/frontend/src/hooks/api/auditLogStreams/types/providers/qradar-provider.ts new file mode 100644 index 000000000..d97226f4a --- /dev/null +++ b/frontend/src/hooks/api/auditLogStreams/types/providers/qradar-provider.ts @@ -0,0 +1,7 @@ +import { LogProvider } from "../../enums"; +import { TRootProviderLogStream } from "./root-provider"; + +export type TQRadarProviderLogStream = TRootProviderLogStream & { + provider: LogProvider.QRadar; + // credentials: {}; +}; diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx index 26708e17b..12d2e9cb6 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx @@ -2,9 +2,10 @@ import { useMemo } from "react"; import { faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { EmptyState, Spinner } from "@app/components/v2"; import { AUDIT_LOG_STREAM_PROVIDER_MAP } from "@app/helpers/auditLogStreams"; -import { usePagination, useResetPageHelper } from "@app/hooks"; +import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useGetAuditLogStreamOptions } from "@app/hooks/api"; import { LogProvider } from "@app/hooks/api/auditLogStreams/enums"; @@ -15,6 +16,8 @@ type Props = { // TODO: When we have more than 1 page of providers, uncomment the search components export const LogStreamProviderSelect = ({ onSelect }: Props) => { + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"]); + const { isPending, data: logStreamOptions } = useGetAuditLogStreamOptions(); const { search, setPage, page, perPage, offset } = usePagination("", { @@ -23,7 +26,11 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => { const filteredOptions = useMemo( () => - (logStreamOptions || []) + [ + ...(logStreamOptions || []), + // QRadar is a planned provider + { name: "IBM QRadar", provider: LogProvider.QRadar } + ] .filter( ({ name, provider }) => name.toLowerCase().includes(search.trim().toLowerCase()) || @@ -54,13 +61,6 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => { return (
- {/* setSearch(e.target.value)} - leftIcon={} - placeholder="Search options..." - className="bg-mineshaft-800 placeholder:text-mineshaft-400" - /> */}
{filteredOptions.slice(offset, perPage * page)?.map((option) => { const { image, icon, name, size = 50 } = AUDIT_LOG_STREAM_PROVIDER_MAP[option.provider]; @@ -68,7 +68,13 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => { return (
- {/* {Boolean(filteredOptions.length) && ( - -

Infisical is constantly adding support for more providers.

-

- {`If you don't see the third-party - provider you're looking for,`}{" "} - - let us know on Slack - {" "} - or{" "} - - make a request on GitHub - - . -

- - } - > -
- - Don't see the third-party provider you're looking for? - - -
- - } - count={filteredOptions.length} - page={page} - perPage={perPage} - onChangePage={setPage} - onChangePerPage={setPerPage} - perPageList={[16]} - /> - )} */} + handlePopUpToggle("upgradePlan", isOpen)} + text="This audit log stream provider requires an enterprise license." + />
); };