diff --git a/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts b/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts index 2ead58bef..3247ef21b 100644 --- a/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts +++ b/backend/src/ee/services/dynamic-secret/providers/elastic-search.ts @@ -17,7 +17,7 @@ const generateUsername = () => { return alphaNumericNanoId(32); }; -export const ElasticSearchDatabaseProvider = (): TDynamicProviderFns => { +export const ElasticSearchProvider = (): TDynamicProviderFns => { const validateProviderInputs = async (inputs: unknown) => { const appCfg = getConfig(); const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not diff --git a/backend/src/ee/services/dynamic-secret/providers/index.ts b/backend/src/ee/services/dynamic-secret/providers/index.ts index ad7de408f..6ae22c869 100644 --- a/backend/src/ee/services/dynamic-secret/providers/index.ts +++ b/backend/src/ee/services/dynamic-secret/providers/index.ts @@ -1,10 +1,11 @@ import { AwsElastiCacheDatabaseProvider } from "./aws-elasticache"; import { AwsIamProvider } from "./aws-iam"; import { CassandraProvider } from "./cassandra"; -import { ElasticSearchDatabaseProvider } from "./elastic-search"; +import { ElasticSearchProvider } from "./elastic-search"; import { DynamicSecretProviders } from "./models"; import { MongoAtlasProvider } from "./mongo-atlas"; import { MongoDBProvider } from "./mongo-db"; +import { RabbitMqProvider } from "./rabbit-mq"; import { RedisDatabaseProvider } from "./redis"; import { SqlDatabaseProvider } from "./sql-database"; @@ -15,6 +16,7 @@ export const buildDynamicSecretProviders = () => ({ [DynamicSecretProviders.Redis]: RedisDatabaseProvider(), [DynamicSecretProviders.AwsElastiCache]: AwsElastiCacheDatabaseProvider(), [DynamicSecretProviders.MongoAtlas]: MongoAtlasProvider(), - [DynamicSecretProviders.ElasticSearch]: ElasticSearchDatabaseProvider(), - [DynamicSecretProviders.MongoDB]: MongoDBProvider() + [DynamicSecretProviders.MongoDB]: MongoDBProvider(), + [DynamicSecretProviders.ElasticSearch]: ElasticSearchProvider(), + [DynamicSecretProviders.RabbitMq]: RabbitMqProvider() }); diff --git a/backend/src/ee/services/dynamic-secret/providers/models.ts b/backend/src/ee/services/dynamic-secret/providers/models.ts index ae8f25581..f23a60df7 100644 --- a/backend/src/ee/services/dynamic-secret/providers/models.ts +++ b/backend/src/ee/services/dynamic-secret/providers/models.ts @@ -56,6 +56,26 @@ export const DynamicSecretElasticSearchSchema = z.object({ ca: z.string().optional() }); +export const DynamicSecretRabbitMqSchema = z.object({ + host: z.string().trim().min(1), + port: z.number(), + tags: z.array(z.string().trim()).default([]), + + username: z.string().trim().min(1), + password: z.string().trim().min(1), + + ca: z.string().optional(), + + virtualHost: z.object({ + name: z.string().trim().min(1), + permissions: z.object({ + read: z.string().trim().min(1), + write: z.string().trim().min(1), + configure: z.string().trim().min(1) + }) + }) +}); + export const DynamicSecretSqlDBSchema = z.object({ client: z.nativeEnum(SqlProviders), host: z.string().trim().toLowerCase(), @@ -154,7 +174,8 @@ export enum DynamicSecretProviders { AwsElastiCache = "aws-elasticache", MongoAtlas = "mongo-db-atlas", ElasticSearch = "elastic-search", - MongoDB = "mongo-db" + MongoDB = "mongo-db", + RabbitMq = "rabbit-mq" } export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [ @@ -165,7 +186,8 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal(DynamicSecretProviders.AwsElastiCache), inputs: DynamicSecretAwsElastiCacheSchema }), z.object({ type: z.literal(DynamicSecretProviders.MongoAtlas), inputs: DynamicSecretMongoAtlasSchema }), z.object({ type: z.literal(DynamicSecretProviders.ElasticSearch), inputs: DynamicSecretElasticSearchSchema }), - z.object({ type: z.literal(DynamicSecretProviders.MongoDB), inputs: DynamicSecretMongoDBSchema }) + z.object({ type: z.literal(DynamicSecretProviders.MongoDB), inputs: DynamicSecretMongoDBSchema }), + z.object({ type: z.literal(DynamicSecretProviders.RabbitMq), inputs: DynamicSecretRabbitMqSchema }) ]); export type TDynamicProviderFns = { diff --git a/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts b/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts new file mode 100644 index 000000000..15492cd2d --- /dev/null +++ b/backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts @@ -0,0 +1,172 @@ +import axios, { Axios } from "axios"; +import https from "https"; +import { customAlphabet } from "nanoid"; +import { z } from "zod"; + +import { getConfig } from "@app/lib/config/env"; +import { BadRequestError } from "@app/lib/errors"; +import { removeTrailingSlash } from "@app/lib/fn"; +import { logger } from "@app/lib/logger"; +import { alphaNumericNanoId } from "@app/lib/nanoid"; + +import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models"; + +const generatePassword = () => { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#"; + return customAlphabet(charset, 64)(); +}; + +const generateUsername = () => { + return alphaNumericNanoId(32); +}; + +type TCreateRabbitMQUser = { + axiosInstance: Axios; + createUser: { + username: string; + password: string; + tags: string[]; + }; + virtualHost: { + name: string; + permissions: { + read: string; + write: string; + configure: string; + }; + }; +}; + +type TDeleteRabbitMqUser = { + axiosInstance: Axios; + usernameToDelete: string; +}; + +async function createRabbitMqUser({ axiosInstance, createUser, virtualHost }: TCreateRabbitMQUser): Promise { + try { + // Create user + const userUrl = `/users/${createUser.username}`; + const userData = { + password: createUser.password, + tags: createUser.tags.join(",") + }; + + await axiosInstance.put(userUrl, userData); + + // Set permissions for the virtual host + if (virtualHost) { + const permissionData = { + configure: virtualHost.permissions.configure, + write: virtualHost.permissions.write, + read: virtualHost.permissions.read + }; + + await axiosInstance.put( + `/permissions/${encodeURIComponent(virtualHost.name)}/${createUser.username}`, + permissionData + ); + } + } catch (error) { + logger.error(error, "Error creating RabbitMQ user"); + throw error; + } +} + +async function deleteRabbitMqUser({ axiosInstance, usernameToDelete }: TDeleteRabbitMqUser) { + await axiosInstance.delete(`users/${usernameToDelete}`); + return { username: usernameToDelete }; +} + +export const RabbitMqProvider = (): TDynamicProviderFns => { + const validateProviderInputs = async (inputs: unknown) => { + const appCfg = getConfig(); + const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not + + const providerInputs = await DynamicSecretRabbitMqSchema.parseAsync(inputs); + if ( + isCloud && + // localhost + // internal ips + (providerInputs.host === "host.docker.internal" || + providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) || + providerInputs.host.match(/^192\.168\.\d+\.\d+/)) + ) { + throw new BadRequestError({ message: "Invalid db host" }); + } + if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1") { + throw new BadRequestError({ message: "Invalid db host" }); + } + + return providerInputs; + }; + + const getClient = async (providerInputs: z.infer) => { + const axiosInstance = axios.create({ + baseURL: `${removeTrailingSlash(providerInputs.host)}:${providerInputs.port}/api`, + auth: { + username: providerInputs.username, + password: providerInputs.password + }, + headers: { + "Content-Type": "application/json" + }, + + ...(providerInputs.ca && { + httpsAgent: new https.Agent({ ca: providerInputs.ca, rejectUnauthorized: false }) + }) + }); + + return axiosInstance; + }; + + const validateConnection = async (inputs: unknown) => { + const providerInputs = await validateProviderInputs(inputs); + const connection = await getClient(providerInputs); + + const infoResponse = await connection.get("/whoami").then(() => true); + + return infoResponse; + }; + + const create = async (inputs: unknown) => { + const providerInputs = await validateProviderInputs(inputs); + const connection = await getClient(providerInputs); + + const username = generateUsername(); + const password = generatePassword(); + + await createRabbitMqUser({ + axiosInstance: connection, + virtualHost: providerInputs.virtualHost, + createUser: { + password, + username, + tags: [...(providerInputs.tags ?? []), "infisical-user"] + } + }); + + return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } }; + }; + + const revoke = async (inputs: unknown, entityId: string) => { + const providerInputs = await validateProviderInputs(inputs); + const connection = await getClient(providerInputs); + + await deleteRabbitMqUser({ axiosInstance: connection, usernameToDelete: entityId }); + + return { entityId }; + }; + + const renew = async (inputs: unknown, entityId: string) => { + // Do nothing + return { entityId }; + }; + + return { + validateProviderInputs, + validateConnection, + create, + revoke, + renew + }; +}; diff --git a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx index c37de08ff..225b884cb 100644 --- a/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx +++ b/docs/documentation/platform/dynamic-secrets/aws-elasticache.mdx @@ -58,7 +58,7 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret. - ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button-redis.png) + ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-aws-elasti-cache.png) @@ -116,7 +116,7 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. - ![Provision Lease](/images/platform/dynamic-secrets/provision-lease-redis.png) + ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret. @@ -125,7 +125,7 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. - ![Provision Lease](/images/platform/dynamic-secrets/lease-values-redis.png) + ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) @@ -133,11 +133,11 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard. This will allow you see the expiration time of the lease or delete a lease before it's set time to live. -![Provision Lease](/images/platform/dynamic-secrets/lease-data-redis.png) +![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) ## Renew Leases To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** as illustrated below. -![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew-redis.png) +![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png) Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret diff --git a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx index cdcb6ce2b..0b2897790 100644 --- a/docs/documentation/platform/dynamic-secrets/elastic-search.mdx +++ b/docs/documentation/platform/dynamic-secrets/elastic-search.mdx @@ -23,7 +23,7 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret. - ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button-redis.png) + ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-elastic-search.png) @@ -99,7 +99,7 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. - ![Provision Lease](/images/platform/dynamic-secrets/provision-lease-redis.png) + ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret. @@ -108,7 +108,7 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. - ![Provision Lease](/images/platform/dynamic-secrets/lease-values-elastic-search.png) + ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) @@ -116,11 +116,11 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard. This will allow you see the expiration time of the lease or delete a lease before it's set time to live. -![Provision Lease](/images/platform/dynamic-secrets/lease-data-redis.png) +![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) ## Renew Leases To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** as illustrated below. -![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew-redis.png) +![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png) Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret diff --git a/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx new file mode 100644 index 000000000..f8649b727 --- /dev/null +++ b/docs/documentation/platform/dynamic-secrets/rabbit-mq.mdx @@ -0,0 +1,116 @@ +--- +title: "RabbitMQ" +description: "Learn how to dynamically generate RabbitMQ user credentials." +--- + +The Infisical RabbitMQ dynamic secret allows you to generate RabbitMQ credentials on demand based on configured role. + +## Prerequisites + +1. Ensure that the `management` plugin is enabled on your RabbitMQ instance. This is required for the dynamic secret to work. + + +## Set up Dynamic Secrets with RabbitMQ + + + + Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret. + + + ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) + + + ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-rabbit-mq.png) + + + + Name by which you want the secret to be referenced + + + + Default time-to-live for a generated secret (it is possible to modify this value when a secret is generate) + + + + Maximum time-to-live for a generated secret. + + + + Your RabbitMQ host. This must be in HTTP format. _(Example: http://your-cluster-ip)_ + + + + The port that the RabbitMQ management plugin is listening on. This is `15672` by default. + + + + The name of the virtual host that the user will be assigned to. This defaults to `/`. + + + + The permissions that the user will have on the virtual host. This defaults to `.*`. + + The three permission fields all take a regular expression _(regex)_, that should match resource names for which the user is granted read / write / configuration permissions + + + + + The username of the user that will be used to provision new dynamic secret leases. + + + + The password of the user that will be used to provision new dynamic secret leases. + + + + A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service. + + + ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png) + + + + + After submitting the form, you will see a dynamic secret created in the dashboard. + + + If this step fails, you may have to add the CA certificate. + + + + + Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. + To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item. + Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section. + + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate-redis.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty-redis.png) + + When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. + + ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) + + + Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret. + + + + Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. + + ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) + + + +## Audit or Revoke Leases +Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard. +This will allow you see the expiration time of the lease or delete a lease before it's set time to live. + +![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) + +## Renew Leases +To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** as illustrated below. +![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png) + + + Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret + diff --git a/docs/documentation/platform/dynamic-secrets/redis.mdx b/docs/documentation/platform/dynamic-secrets/redis.mdx index 1270e6959..cb2e6a17e 100644 --- a/docs/documentation/platform/dynamic-secrets/redis.mdx +++ b/docs/documentation/platform/dynamic-secrets/redis.mdx @@ -16,7 +16,7 @@ Create a user with the required permission in your Redis instance. This user wil Open the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret. - ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button-redis.png) + ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-redis.png) @@ -78,7 +78,7 @@ Create a user with the required permission in your Redis instance. This user wil When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. - ![Provision Lease](/images/platform/dynamic-secrets/provision-lease-redis.png) + ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret. @@ -87,7 +87,7 @@ Create a user with the required permission in your Redis instance. This user wil Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. - ![Provision Lease](/images/platform/dynamic-secrets/lease-values-redis.png) + ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) @@ -95,11 +95,11 @@ Create a user with the required permission in your Redis instance. This user wil Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard. This will allow you see the expiration time of the lease or delete a lease before it's set time to live. -![Provision Lease](/images/platform/dynamic-secrets/lease-data-redis.png) +![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) ## Renew Leases To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** as illustrated below. -![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew-redis.png) +![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png) Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret diff --git a/docs/images/platform/dynamic-secrets/add-dynamic-secret-button-redis.png b/docs/images/platform/dynamic-secrets/add-dynamic-secret-button-redis.png deleted file mode 100644 index 537f20e73..000000000 Binary files a/docs/images/platform/dynamic-secrets/add-dynamic-secret-button-redis.png and /dev/null differ diff --git a/docs/images/platform/dynamic-secrets/add-dynamic-secret-button.png b/docs/images/platform/dynamic-secrets/add-dynamic-secret-button.png index 8d0fd3ecc..537f20e73 100644 Binary files a/docs/images/platform/dynamic-secrets/add-dynamic-secret-button.png and b/docs/images/platform/dynamic-secrets/add-dynamic-secret-button.png differ diff --git a/docs/images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png b/docs/images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png new file mode 100644 index 000000000..41508b465 Binary files /dev/null and b/docs/images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png differ diff --git a/docs/images/platform/dynamic-secrets/dynamic-secret-lease-renew-redis.png b/docs/images/platform/dynamic-secrets/dynamic-secret-lease-renew-redis.png deleted file mode 100644 index a2fd14d41..000000000 Binary files a/docs/images/platform/dynamic-secrets/dynamic-secret-lease-renew-redis.png and /dev/null differ diff --git a/docs/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png b/docs/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png index e97554415..3d2e32e8a 100644 Binary files a/docs/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png and b/docs/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png differ diff --git a/docs/images/platform/dynamic-secrets/dynamic-secret-rabbit-mq-modal.png b/docs/images/platform/dynamic-secrets/dynamic-secret-rabbit-mq-modal.png new file mode 100644 index 000000000..885a17a88 Binary files /dev/null and b/docs/images/platform/dynamic-secrets/dynamic-secret-rabbit-mq-modal.png differ diff --git a/docs/images/platform/dynamic-secrets/lease-data-redis.png b/docs/images/platform/dynamic-secrets/lease-data-redis.png deleted file mode 100644 index f3fd51637..000000000 Binary files a/docs/images/platform/dynamic-secrets/lease-data-redis.png and /dev/null differ diff --git a/docs/images/platform/dynamic-secrets/lease-data.png b/docs/images/platform/dynamic-secrets/lease-data.png index aecd8c11d..9562da1b5 100644 Binary files a/docs/images/platform/dynamic-secrets/lease-data.png and b/docs/images/platform/dynamic-secrets/lease-data.png differ diff --git a/docs/images/platform/dynamic-secrets/lease-values-elastic-search.png b/docs/images/platform/dynamic-secrets/lease-values-elastic-search.png deleted file mode 100644 index 7d5685a81..000000000 Binary files a/docs/images/platform/dynamic-secrets/lease-values-elastic-search.png and /dev/null differ diff --git a/docs/images/platform/dynamic-secrets/lease-values-redis.png b/docs/images/platform/dynamic-secrets/lease-values-redis.png deleted file mode 100644 index 95d4a4ffd..000000000 Binary files a/docs/images/platform/dynamic-secrets/lease-values-redis.png and /dev/null differ diff --git a/docs/images/platform/dynamic-secrets/lease-values.png b/docs/images/platform/dynamic-secrets/lease-values.png index d552845f8..962bd76ec 100644 Binary files a/docs/images/platform/dynamic-secrets/lease-values.png and b/docs/images/platform/dynamic-secrets/lease-values.png differ diff --git a/docs/images/platform/dynamic-secrets/provision-lease-redis.png b/docs/images/platform/dynamic-secrets/provision-lease-redis.png deleted file mode 100644 index f5237d058..000000000 Binary files a/docs/images/platform/dynamic-secrets/provision-lease-redis.png and /dev/null differ diff --git a/docs/images/platform/dynamic-secrets/provision-lease.png b/docs/images/platform/dynamic-secrets/provision-lease.png index f144a5ae2..96b0505b9 100644 Binary files a/docs/images/platform/dynamic-secrets/provision-lease.png and b/docs/images/platform/dynamic-secrets/provision-lease.png differ diff --git a/docs/mint.json b/docs/mint.json index c26245def..5528a22de 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -165,6 +165,7 @@ "documentation/platform/dynamic-secrets/redis", "documentation/platform/dynamic-secrets/aws-elasticache", "documentation/platform/dynamic-secrets/elastic-search", + "documentation/platform/dynamic-secrets/rabbit-mq", "documentation/platform/dynamic-secrets/aws-iam", "documentation/platform/dynamic-secrets/mongo-atlas", "documentation/platform/dynamic-secrets/mongo-db" diff --git a/frontend/src/hooks/api/dynamicSecret/types.ts b/frontend/src/hooks/api/dynamicSecret/types.ts index e7e9e3318..32c9b15d0 100644 --- a/frontend/src/hooks/api/dynamicSecret/types.ts +++ b/frontend/src/hooks/api/dynamicSecret/types.ts @@ -23,7 +23,8 @@ export enum DynamicSecretProviders { AwsElastiCache = "aws-elasticache", MongoAtlas = "mongo-db-atlas", ElasticSearch = "elastic-search", - MongoDB = "mongo-db" + MongoDB = "mongo-db", + RabbitMq = "rabbit-mq" } export enum SqlProviders { @@ -155,6 +156,27 @@ export type TDynamicSecretProvider = apiKeyId: string; }; }; + } + | { + type: DynamicSecretProviders.RabbitMq; + inputs: { + host: string; + port: number; + + username: string; + password: string; + + tags: string[]; + virtualHost: { + name: string; + permissions: { + configure: string; + write: string; + read: string; + }; + }; + ca?: string; + }; }; export type TCreateDynamicSecretDTO = { diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx index 19c3191a4..90eb74012 100644 --- a/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx +++ b/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { DiRedis } from "react-icons/di"; -import { SiApachecassandra, SiElasticsearch, SiMongodb } from "react-icons/si"; +import { SiApachecassandra, SiElasticsearch, SiMongodb, SiRabbitmq } from "react-icons/si"; import { faAws } from "@fortawesome/free-brands-svg-icons"; import { faDatabase } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -15,6 +15,7 @@ import { CassandraInputForm } from "./CassandraInputForm"; import { ElasticSearchInputForm } from "./ElasticSearchInputForm"; import { MongoAtlasInputForm } from "./MongoAtlasInputForm"; import { MongoDBDatabaseInputForm } from "./MongoDBInputForm"; +import { RabbitMqInputForm } from "./RabbitMqInputForm"; import { RedisInputForm } from "./RedisInputForm"; import { SqlDatabaseInputForm } from "./SqlDatabaseInputForm"; @@ -71,6 +72,11 @@ const DYNAMIC_SECRET_LIST = [ icon: , provider: DynamicSecretProviders.ElasticSearch, title: "Elastic Search" + }, + { + icon: , + provider: DynamicSecretProviders.RabbitMq, + title: "RabbitMQ" } ]; @@ -276,6 +282,24 @@ export const CreateDynamicSecretForm = ({ /> )} + {wizardStep === WizardSteps.ProviderInputs && + selectedProvider === DynamicSecretProviders.RabbitMq && ( + + + + )} diff --git a/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx new file mode 100644 index 000000000..dae0cd478 --- /dev/null +++ b/frontend/src/views/SecretMainPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx @@ -0,0 +1,421 @@ +import { Controller, useForm } from "react-hook-form"; +import Link from "next/link"; +import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import ms from "ms"; +import { z } from "zod"; + +import { TtlFormLabel } from "@app/components/features"; +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2"; +import { useCreateDynamicSecret } from "@app/hooks/api"; +import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; + +const formSchema = z.object({ + provider: z.object({ + host: z.string().trim().min(1), + port: z.coerce.number(), // important: this is the management plugin port + + username: z.string(), + password: z.string(), + + tags: z.array(z.string().trim()), + virtualHost: z.object({ + name: z.string().trim().min(1), + permissions: z.object({ + read: z.string().trim().min(1), + write: z.string().trim().min(1), + configure: z.string().trim().min(1) + }) + }), + ca: z.string().optional() + }), + defaultTTL: z.string().superRefine((val, ctx) => { + const valMs = ms(val); + if (valMs < 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); + // a day + if (valMs > 24 * 60 * 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + }), + maxTTL: z + .string() + .optional() + .superRefine((val, ctx) => { + if (!val) return; + const valMs = ms(val); + if (valMs < 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); + // a day + if (valMs > 24 * 60 * 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + }), + name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") +}); +type TForm = z.infer; + +type Props = { + onCompleted: () => void; + onCancel: () => void; + secretPath: string; + projectSlug: string; + environment: string; +}; + +export const RabbitMqInputForm = ({ + onCompleted, + onCancel, + environment, + secretPath, + projectSlug +}: Props) => { + const { + control, + formState: { isSubmitting }, + handleSubmit, + setValue, + watch + } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + provider: { + port: 15672, + virtualHost: { + name: "/", + permissions: { + read: ".*", + write: ".*", + configure: ".*" + } + }, + tags: [] + } + } + }); + + const createDynamicSecret = useCreateDynamicSecret(); + + const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { + // wait till previous request is finished + if (createDynamicSecret.isLoading) return; + try { + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment + }); + onCompleted(); + } catch (err) { + createNotification({ + type: "error", + text: "Failed to create dynamic secret" + }); + } + }; + + const selectedTags = watch("provider.tags"); + + return ( +
+
+
+
+
+ ( + + + + )} + /> +
+
+ ( + } + isError={Boolean(error?.message)} + errorText={error?.message} + > + + + )} + /> +
+
+ ( + } + isError={Boolean(error?.message)} + errorText={error?.message} + > + + + )} + /> +
+
+
+
+ Configuration +
+
+
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ +
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ +
+ +
+ ( + + + + )} + /> + + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> +
+
+ +
+ +

Select which tag(s) to assign the users provisioned by Infisical.

+

+ There is a wide range of in-built roles in RabbitMQ. Some include, + management, policymaker, monitoring, administrator.
+ + + + Read more about management tags here + + + + . +

+

+ You can also assign custom roles by providing the name of the custom role in + the input field. +

+
+ } + /> +
+ {selectedTags.map((_, i) => ( + ( + +
+ + { + if (selectedTags && selectedTags?.length > 1) { + setValue( + "provider.tags", + selectedTags.filter((__, idx) => idx !== i) + ); + } + }} + > + + +
+
+ )} + /> + ))} +
+
+
+ +
+
+ ( + + + + )} + /> +
+
+
+
+
+ + +
+ + + ); +}; diff --git a/frontend/src/views/SecretMainPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx index 100e2de91..85be20fc1 100644 --- a/frontend/src/views/SecretMainPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx +++ b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx @@ -140,6 +140,24 @@ const renderOutputForm = (provider: DynamicSecretProviders, data: unknown) => { ); } + if (provider === DynamicSecretProviders.RabbitMq) { + const { DB_USERNAME, DB_PASSWORD } = data as { + DB_USERNAME: string; + DB_PASSWORD: string; + }; + + return ( +
+ + +
+ ); + } + if (provider === DynamicSecretProviders.ElasticSearch) { const { DB_USERNAME, DB_PASSWORD } = data as { DB_USERNAME: string; diff --git a/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretForm.tsx b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretForm.tsx index 61efdb42e..8f95bcc94 100644 --- a/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretForm.tsx +++ b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretForm.tsx @@ -10,6 +10,7 @@ import { EditDynamicSecretCassandraForm } from "./EditDynamicSecretCassandraForm import { EditDynamicSecretElasticSearchForm } from "./EditDynamicSecretElasticSearchForm"; import { EditDynamicSecretMongoAtlasForm } from "./EditDynamicSecretMongoAtlasForm"; import { EditDynamicSecretMongoDBForm } from "./EditDynamicSecretMongoDBForm"; +import { EditDynamicSecretRabbitMqForm } from "./EditDynamicSecretRabbitMqForm"; import { EditDynamicSecretRedisProviderForm } from "./EditDynamicSecretRedisProviderForm"; import { EditDynamicSecretSqlProviderForm } from "./EditDynamicSecretSqlProviderForm"; @@ -183,6 +184,24 @@ export const EditDynamicSecretForm = ({ /> )} + + {dynamicSecretDetails?.type === DynamicSecretProviders.RabbitMq && ( + + + + )} ); }; diff --git a/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx new file mode 100644 index 000000000..a28c257b0 --- /dev/null +++ b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx @@ -0,0 +1,421 @@ +import { Controller, useForm } from "react-hook-form"; +import Link from "next/link"; +import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import ms from "ms"; +import { z } from "zod"; + +import { TtlFormLabel } from "@app/components/features"; +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2"; +import { useUpdateDynamicSecret } from "@app/hooks/api"; +import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types"; + +const formSchema = z.object({ + inputs: z.object({ + host: z.string().trim().min(1), + port: z.coerce.number(), // important: this is the management plugin port + + username: z.string(), + password: z.string(), + + tags: z.array(z.string().trim()), + virtualHost: z.object({ + name: z.string().trim().min(1), + permissions: z.object({ + read: z.string().trim().min(1), + write: z.string().trim().min(1), + configure: z.string().trim().min(1) + }) + }), + ca: z.string().optional() + }), + defaultTTL: z.string().superRefine((val, ctx) => { + const valMs = ms(val); + if (valMs < 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); + // a day + if (valMs > 24 * 60 * 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + }), + maxTTL: z + .string() + .optional() + .superRefine((val, ctx) => { + if (!val) return; + const valMs = ms(val); + if (valMs < 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" }); + // a day + if (valMs > 24 * 60 * 60 * 1000) + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); + }), + newName: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") +}); +type TForm = z.infer; + +type Props = { + onClose: () => void; + dynamicSecret: TDynamicSecret & { inputs: unknown }; + secretPath: string; + environment: string; + projectSlug: string; +}; +export const EditDynamicSecretRabbitMqForm = ({ + onClose, + dynamicSecret, + secretPath, + environment, + projectSlug +}: Props) => { + const { + control, + formState: { isSubmitting }, + handleSubmit, + setValue, + watch + } = useForm({ + resolver: zodResolver(formSchema), + values: { + defaultTTL: dynamicSecret.defaultTTL, + maxTTL: dynamicSecret.maxTTL, + newName: dynamicSecret.name, + inputs: { + ...(dynamicSecret.inputs as TForm["inputs"]) + } + } + }); + + const updateDynamicSecret = useUpdateDynamicSecret(); + + const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { + // wait till previous request is finished + if (updateDynamicSecret.isLoading) return; + try { + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); + } catch (err) { + createNotification({ + type: "error", + text: "Failed to update dynamic secret" + }); + } + }; + + const selectedTags = watch("inputs.tags"); + + return ( +
+
+
+
+
+ ( + + + + )} + /> +
+
+ ( + } + isError={Boolean(error?.message)} + errorText={error?.message} + > + + + )} + /> +
+
+ ( + } + isError={Boolean(error?.message)} + errorText={error?.message} + > + + + )} + /> +
+
+
+
+ Configuration +
+
+
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ +
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ +
+ +
+ ( + + + + )} + /> + + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> +
+
+ +
+ +

Select which tag(s) to assign the users provisioned by Infisical.

+

+ There is a wide range of in-built roles in RabbitMQ. Some include, + management, policymaker, monitoring, administrator.
+ + + + Read more about management tags here + + + + . +

+

+ You can also assign custom roles by providing the name of the custom role in + the input field. +

+
+ } + /> +
+ {selectedTags.map((_, i) => ( + ( + +
+ + { + if (selectedTags && selectedTags?.length > 1) { + setValue( + "inputs.tags", + selectedTags.filter((__, idx) => idx !== i) + ); + } + }} + > + + +
+
+ )} + /> + ))} +
+
+
+ +
+
+ ( + + + + )} + /> +
+
+
+
+
+ + +
+ + + ); +};