diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js index 9c558919b..b23cf05ae 100644 --- a/backend/.eslintrc.js +++ b/backend/.eslintrc.js @@ -23,16 +23,17 @@ module.exports = { root: true, overrides: [ { - files: ["./e2e-test/**/*"], + files: ["./e2e-test/**/*", "./src/db/migrations/**/*"], rules: { "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-call": "off" } } ], + rules: { "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-unsafe-enum-comparison": "off", diff --git a/backend/src/db/migrations/20240414192520_drop-role-roleid-project-membership.ts b/backend/src/db/migrations/20240414192520_drop-role-roleid-project-membership.ts new file mode 100644 index 000000000..2dd58c5d1 --- /dev/null +++ b/backend/src/db/migrations/20240414192520_drop-role-roleid-project-membership.ts @@ -0,0 +1,47 @@ +import { Knex } from "knex"; + +import { ProjectMembershipRole, TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const doesProjectRoleFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "role"); + const doesProjectRoleIdFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "roleId"); + await knex.schema.alterTable(TableName.ProjectMembership, (t) => { + if (doesProjectRoleFieldExist) t.dropColumn("roleId"); + if (doesProjectRoleIdFieldExist) t.dropColumn("role"); + }); + + const doesIdentityProjectRoleFieldExist = await knex.schema.hasColumn(TableName.IdentityProjectMembership, "role"); + const doesIdentityProjectRoleIdFieldExist = await knex.schema.hasColumn( + TableName.IdentityProjectMembership, + "roleId" + ); + await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => { + if (doesIdentityProjectRoleFieldExist) t.dropColumn("roleId"); + if (doesIdentityProjectRoleIdFieldExist) t.dropColumn("role"); + }); +} + +export async function down(knex: Knex): Promise { + const doesProjectRoleFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "role"); + const doesProjectRoleIdFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "roleId"); + await knex.schema.alterTable(TableName.ProjectMembership, (t) => { + if (!doesProjectRoleFieldExist) t.string("role").defaultTo(ProjectMembershipRole.Member); + if (!doesProjectRoleIdFieldExist) { + t.uuid("roleId"); + t.foreign("roleId").references("id").inTable(TableName.ProjectRoles); + } + }); + + const doesIdentityProjectRoleFieldExist = await knex.schema.hasColumn(TableName.IdentityProjectMembership, "role"); + const doesIdentityProjectRoleIdFieldExist = await knex.schema.hasColumn( + TableName.IdentityProjectMembership, + "roleId" + ); + await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => { + if (!doesIdentityProjectRoleFieldExist) t.string("role").defaultTo(ProjectMembershipRole.Member); + if (!doesIdentityProjectRoleIdFieldExist) { + t.uuid("roleId"); + t.foreign("roleId").references("id").inTable(TableName.ProjectRoles); + } + }); +} diff --git a/backend/src/db/schemas/identity-project-memberships.ts b/backend/src/db/schemas/identity-project-memberships.ts index 276c9581e..2f17c36d8 100644 --- a/backend/src/db/schemas/identity-project-memberships.ts +++ b/backend/src/db/schemas/identity-project-memberships.ts @@ -9,8 +9,6 @@ import { TImmutableDBKeys } from "./models"; export const IdentityProjectMembershipsSchema = z.object({ id: z.string().uuid(), - role: z.string(), - roleId: z.string().uuid().nullable().optional(), projectId: z.string(), identityId: z.string().uuid(), createdAt: z.date(), diff --git a/backend/src/db/schemas/project-memberships.ts b/backend/src/db/schemas/project-memberships.ts index 8576a318e..e522d6280 100644 --- a/backend/src/db/schemas/project-memberships.ts +++ b/backend/src/db/schemas/project-memberships.ts @@ -9,12 +9,10 @@ import { TImmutableDBKeys } from "./models"; export const ProjectMembershipsSchema = z.object({ id: z.string().uuid(), - role: z.string(), createdAt: z.date(), updatedAt: z.date(), userId: z.string().uuid(), - projectId: z.string(), - roleId: z.string().uuid().nullable().optional() + projectId: z.string() }); export type TProjectMemberships = z.infer; diff --git a/backend/src/db/seeds/3-project.ts b/backend/src/db/seeds/3-project.ts index d41efb71c..934130494 100644 --- a/backend/src/db/seeds/3-project.ts +++ b/backend/src/db/seeds/3-project.ts @@ -33,8 +33,7 @@ export async function seed(knex: Knex): Promise { const projectMembership = await knex(TableName.ProjectMembership) .insert({ projectId: project.id, - userId: seedData1.id, - role: ProjectMembershipRole.Admin + userId: seedData1.id }) .returning("*"); await knex(TableName.ProjectUserMembershipRole).insert({ diff --git a/backend/src/db/seeds/4-machine-identity.ts b/backend/src/db/seeds/4-machine-identity.ts index 618c47114..662232e02 100644 --- a/backend/src/db/seeds/4-machine-identity.ts +++ b/backend/src/db/seeds/4-machine-identity.ts @@ -78,8 +78,7 @@ export async function seed(knex: Knex): Promise { const identityProjectMembership = await knex(TableName.IdentityProjectMembership) .insert({ identityId: seedData1.machineIdentity.id, - projectId: seedData1.project.id, - role: ProjectMembershipRole.Admin + projectId: seedData1.project.id }) .returning("*"); diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index b89a3aa3d..d8114388e 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -72,7 +72,6 @@ export const permissionDALFactory = (db: TDbClient) => { .select(selectAllTableCols(TableName.GroupProjectMembershipRole)) .select( db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"), - // TODO(roll-forward-migration): remove this field when we drop this in next migration after a week db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("membershipCreatedAt"), db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("membershipUpdatedAt"), db.ref("projectId").withSchema(TableName.GroupProjectMembership), @@ -105,7 +104,6 @@ export const permissionDALFactory = (db: TDbClient) => { .select(selectAllTableCols(TableName.ProjectUserMembershipRole)) .select( db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"), - // TODO(roll-forward-migration): remove this field when we drop this in next migration after a week db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"), db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"), db.ref("projectId").withSchema(TableName.ProjectMembership), @@ -131,11 +129,10 @@ export const permissionDALFactory = (db: TDbClient) => { const permission = sqlNestRelationships({ data: docs, key: "projectId", - parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt, role }) => ({ + parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({ orgId, orgAuthEnforced, userId, - role, id: membershipId, projectId, createdAt: membershipCreatedAt, @@ -179,18 +176,10 @@ export const permissionDALFactory = (db: TDbClient) => { ? sqlNestRelationships({ data: groupDocs, key: "projectId", - parentMapper: ({ - orgId, - orgAuthEnforced, - membershipId, - membershipCreatedAt, - membershipUpdatedAt, - role - }) => ({ + parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({ orgId, orgAuthEnforced, userId, - role, id: membershipId, projectId, createdAt: membershipCreatedAt, @@ -270,7 +259,6 @@ export const permissionDALFactory = (db: TDbClient) => { .select( db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"), db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project - db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"), db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"), db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"), db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"), @@ -299,11 +287,10 @@ export const permissionDALFactory = (db: TDbClient) => { const permission = sqlNestRelationships({ data: docs, key: "membershipId", - parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, oldRoleField, orgId }) => ({ + parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, orgId }) => ({ id: membershipId, identityId, projectId, - role: oldRoleField, createdAt: membershipCreatedAt, updatedAt: membershipUpdatedAt, orgId, diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index 2b281e6fe..d99b4ec08 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -35,31 +35,28 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider }), response: { 200: z.object({ - memberships: ProjectMembershipsSchema.omit({ role: true }) - .merge( + memberships: ProjectMembershipsSchema.extend({ + user: UsersSchema.pick({ + email: true, + firstName: true, + lastName: true, + id: true + }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + roles: z.array( z.object({ - user: UsersSchema.pick({ - email: true, - firstName: true, - lastName: true, - id: true - }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), - roles: z.array( - z.object({ - id: z.string(), - role: z.string(), - customRoleId: z.string().optional().nullable(), - customRoleName: z.string().optional().nullable(), - customRoleSlug: z.string().optional().nullable(), - isTemporary: z.boolean(), - temporaryMode: z.string().optional().nullable(), - temporaryRange: z.string().nullable().optional(), - temporaryAccessStartTime: z.date().nullable().optional(), - temporaryAccessEndTime: z.date().nullable().optional() - }) - ) + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() }) ) + }) .omit({ createdAt: true, updatedAt: true }) .array() }) diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index d2306e9a2..280e52680 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -70,32 +70,29 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - users: ProjectMembershipsSchema.omit({ role: true }) - .merge( + users: ProjectMembershipsSchema.extend({ + user: UsersSchema.pick({ + email: true, + username: true, + firstName: true, + lastName: true, + id: true + }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + roles: z.array( z.object({ - user: UsersSchema.pick({ - username: true, - email: true, - firstName: true, - lastName: true, - id: true - }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), - roles: z.array( - z.object({ - id: z.string(), - role: z.string(), - customRoleId: z.string().optional().nullable(), - customRoleName: z.string().optional().nullable(), - customRoleSlug: z.string().optional().nullable(), - isTemporary: z.boolean(), - temporaryMode: z.string().optional().nullable(), - temporaryRange: z.string().nullable().optional(), - temporaryAccessStartTime: z.date().nullable().optional(), - temporaryAccessEndTime: z.date().nullable().optional() - }) - ) + id: z.string(), + role: z.string(), + customRoleId: z.string().optional().nullable(), + customRoleName: z.string().optional().nullable(), + customRoleSlug: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional() }) ) + }) .omit({ createdAt: true, updatedAt: true }) .array() }) diff --git a/backend/src/services/identity-project/identity-project-service.ts b/backend/src/services/identity-project/identity-project-service.ts index 2da2492c0..18a1803ac 100644 --- a/backend/src/services/identity-project/identity-project-service.ts +++ b/backend/src/services/identity-project/identity-project-service.ts @@ -93,9 +93,7 @@ export const identityProjectServiceFactory = ({ const identityProjectMembership = await identityProjectDAL.create( { identityId, - projectId: project.id, - role: isCustomRole ? ProjectMembershipRole.Custom : role, - roleId: customRole?.id + projectId: project.id }, tx ); diff --git a/backend/src/services/project/project-queue.ts b/backend/src/services/project/project-queue.ts index 4431855ce..81ecd6da1 100644 --- a/backend/src/services/project/project-queue.ts +++ b/backend/src/services/project/project-queue.ts @@ -232,8 +232,7 @@ export const projectQueueFactory = ({ const projectMembership = await projectMembershipDAL.create( { projectId: project.id, - userId: ghostUser.user.id, - role: ProjectMembershipRole.Admin + userId: ghostUser.user.id }, tx ); diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 9e8d70020..008dac593 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -141,8 +141,7 @@ export const projectServiceFactory = ({ const projectMembership = await projectMembershipDAL.create( { userId: ghostUser.user.id, - projectId: project.id, - role: ProjectMembershipRole.Admin + projectId: project.id }, tx ); @@ -244,8 +243,7 @@ export const projectServiceFactory = ({ const userProjectMembership = await projectMembershipDAL.create( { projectId: project.id, - userId: user.id, - role: projectAdmin.projectRole + userId: user.id }, tx ); @@ -302,9 +300,7 @@ export const projectServiceFactory = ({ const identityProjectMembership = await identityProjectDAL.create( { identityId: actorId, - projectId: project.id, - role: isCustomRole ? ProjectMembershipRole.Custom : ProjectMembershipRole.Admin, - roleId: customRole?.id + projectId: project.id }, tx ); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index f07aeb190..764761098 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -66,6 +66,8 @@ services: environment: - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable command: npm run migration:latest + volumes: + - ./backend/src:/app/src backend: container_name: infisical-dev-api diff --git a/docs/documentation/platform/auth-methods/email-password.mdx b/docs/documentation/platform/auth-methods/email-password.mdx index 82eae1642..db23026b8 100644 --- a/docs/documentation/platform/auth-methods/email-password.mdx +++ b/docs/documentation/platform/auth-methods/email-password.mdx @@ -1,5 +1,5 @@ --- -title: "Email and Pasword" +title: "Email and Password" description: "Learn how to authenticate into Infisical with email and password." --- @@ -9,6 +9,6 @@ It is currently possible to use the **Email and Password** auth method to authen Every **Email and Password** is accompanied by an emergency kit given to users during signup. If the password is lost or forgotten, emergency kit is only way to retrieve the access to your account. It is possible to generate a new emergency kit with the following steps: 1. Open the `Personal Settings` menu. -![open personal settings](../../images/auth-methods/access-personal-settings.png) +![open personal settings](../../../images/auth-methods/access-personal-settings.png) 2. Scroll down to the `Emergency Kit` section. -3. Enter your current password and click `Save`. \ No newline at end of file +3. Enter your current password and click `Save`. diff --git a/docs/images/self-hosting/reference-architectures/on-premise-architecture.png b/docs/images/self-hosting/reference-architectures/on-premise-architecture.png new file mode 100644 index 000000000..a4d04f98d Binary files /dev/null and b/docs/images/self-hosting/reference-architectures/on-premise-architecture.png differ diff --git a/docs/mint.json b/docs/mint.json index 61d5409f8..114af99c3 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -212,7 +212,8 @@ { "group": "Reference architectures", "pages": [ - "self-hosting/reference-architectures/aws-ecs" + "self-hosting/reference-architectures/aws-ecs", + "self-hosting/reference-architectures/on-premise" ] }, "self-hosting/ee", diff --git a/docs/self-hosting/deployment-options/aws-ec2.mdx b/docs/self-hosting/deployment-options/aws-ec2.mdx deleted file mode 100644 index 303df2009..000000000 --- a/docs/self-hosting/deployment-options/aws-ec2.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: "AWS EC2" -description: "Learn to install Infisical on EC2 using Cloud Formation template" ---- - - -This deployment option will use AWS Cloudformation to auto deploy an instance of Infisical on a single EC2 via Docker Compose. - -**Resources that will be provisioned** -- 1 EC2 instance -- 1 DocumentDB cluster -- 1 DocumentDB instance -- Security groups - - -Once installation is complete, you will have to create the first account. No default account is provided. - - - - - \ No newline at end of file diff --git a/docs/self-hosting/deployment-options/aws-lightsail.mdx b/docs/self-hosting/deployment-options/aws-lightsail.mdx deleted file mode 100644 index b5cdb6c9d..000000000 --- a/docs/self-hosting/deployment-options/aws-lightsail.mdx +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: "AWS Lightsail" -description: "Deploy Infisical with AWS Lightsail" ---- - -Prerequisites: -- Have an account with [Amazon Web Services (AWS)](https://aws.amazon.com/) - - - - 1.1. In AWS, navigate to the **Lightsail** service and press **Create container service** under the **Containers** tab. - ![AWS Lightsail](/images/self-hosting/deployment-options/aws-lightsail/awsl-select-lightsail.png) - - ![AWS Lightsail create container service](/images/self-hosting/deployment-options/aws-lightsail/awsl-create-container-service.png) - - 1.2. In the **Container service location** section, select the AWS region that's closest to your infrastructure. - - Afterwards, in the **Container service capacity** section, set the power level and scale to fit your needs; you may opt for the default setting - and adjust accordingly in the future. - - ![AWS Lightsail container service capacity](/images/self-hosting/deployment-options/aws-lightsail/awsl-create-container-service-capacity.png) - - 1.3. In the **Set up your first deployment** section, select the **Specify a custom deployment** option. Give the container a friendly name like **infisical** and fill in your intended [Infisical public Docker image](https://hub.docker.com/r/infisical/infisical) in the **Image** field; this will pull the image from Docker Hub. - - For example, in order to opt for Infisical `v0.43.4`, you would input: `infisical/infisical:v0.43.4`. - - ![AWS Lightsail container service deployment](/images/self-hosting/deployment-options/aws-lightsail/awsl-create-container-service-deployment.png) - - 1.4. Running Infisical requires a few environment variables to be set for the container service. - At minimum, Infisical requires that you set the variables `ENCRYPTION_KEY`, `AUTH_SECRET`, `MONGO_URL`, and `REDIS_URL` - which you can read more about [here](/self-hosting/configuration/envars). - - In the **Environment variables** section, fill in the required environment variables. - - - To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars). - - - Also, under the **Open ports** section, add an entry for port `8080` and protocol `HTTP` since Infisical listens on port `8080`. - - ![AWS Lightsail container service environment variables](/images/self-hosting/deployment-options/aws-lightsail/awsl-create-container-service-envars.png) - - 1.5. In the **Public endpoint** section, select the container from the previous steps from the dropdown; this will make the container accessible over the public internet. - - ![AWS Lightsail container service public endpoint](/images/self-hosting/deployment-options/aws-lightsail/awsl-create-container-service-public-endpoint.png) - - 1.6. Finally, in the **Identify your service** section, give the container service a unique name like infisical and press **Create container service**. - - ![AWS Lightsail container service summary](/images/self-hosting/deployment-options/aws-lightsail/awsl-create-container-service-summary.png) - - - On the newly-created container service page, wait for the **Status** to turn to **Running** and check out the **Public domain** of the container service; you can access your instance of Infisical by this URL. - - ![AWS Lightsail container service overview](/images/self-hosting/deployment-options/aws-lightsail/awsl-container-service-overview.png) - - - - - - Yes, here are a few that come to mind: - - In step 1.3, we recommend pinning the Docker image to a specific [version of Infisical](https://hub.docker.com/r/infisical/infisical/tags) - instead of referring to the `latest` tag to avoid any unexpected version-to-version migration issues. - - We're working on putting together a fuller list of deployment best practices as well as minimum resource configuration requirements for running Infisical so stay tuned! - - \ No newline at end of file diff --git a/docs/self-hosting/deployment-options/azure-app-services.mdx b/docs/self-hosting/deployment-options/azure-app-services.mdx deleted file mode 100644 index a8472ae2b..000000000 --- a/docs/self-hosting/deployment-options/azure-app-services.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: "Azure App Services" -description: "Deploy Infisical with Azure App Service" ---- - -Prerequisites: - - Have an account with [Microsoft Azure](https://azure.microsoft.com/en-us) - - - - 1.1. In Azure, navigate to the **App Services** solution and press **Create > Web App**. - - ![Azure app services](/images/self-hosting/deployment-options/azure-app-services/aas-select-app-services.png) - - ![Azure create app service](/images/self-hosting/deployment-options/azure-app-services/aas-create-app-service.png) - - 1.2. In the **Basics** section, specify the **Subscription** and **Resource group** to manage the deployed resource. - - Also, give the container a friendly name like Infisical and specify a **Region** for it to be deployed to. - - ![Azure app service basics](/images/self-hosting/deployment-options/azure-app-services/aas-create-app-service-basics.png) - - 1.3. In the **Docker** section, select the **Single Container** option under **Options** and specify **Docker Hub** as the image source - - Next, under the **Docker hub options** sub-section, select the **Public** option under **Access Type** and fill in your intended [Infisical public Docker image](https://hub.docker.com/r/infisical/infisical) in the **Image and tag** field; this will pull the image from Docker Hub. - - For example, in order to opt for Infisical `v0.43.4`, you would input: `infisical/infisical:v0.43.4`. - - ![Azure app service docker](/images/self-hosting/deployment-options/azure-app-services/aas-create-app-service-docker.png) - - 1.4. Finally, in the **Review + create** section, double check the information from the previous steps and press **Create** to create the Azure app service. - - ![Azure app service review](/images/self-hosting/deployment-options/azure-app-services/aas-create-app-service-review.png) - - 1.5. Next, wait a minute or two on the deployment overview page for the app to be created. Once the deployment is complete, press **Go to resource** - to head to the **App Service dashboard** for the newly-created app. - - ![Azure app service deployment complete](/images/self-hosting/deployment-options/azure-app-services/aas-app-service-deployment-complete.png) - - 1.6. Running Infisical requires a few environment variables to be set for the Azure app service. - At minimum, Infisical requires that you set the variables `ENCRYPTION_KEY`, `AUTH_SECRET`, `MONGO_URL`, and `REDIS_URL` - which you can read more about [here](/self-hosting/configuration/envars). - - - To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars). - - - Additionally, you must set the variable `WEBSITES_PORT=8080` since - Infisical listens on port `8080`. - - In the **Settings > Configuration** section of the newly-created app service, fill in the required environment variables. - - ![Azure app service deployment complete](/images/self-hosting/deployment-options/azure-app-services/aas-app-service-configuration.png) - - - In the **Overview** section, check out the **Default domain** for your instance of Infisical; you can visit the instance at this URL. - - ![Azure app service deployment complete](/images/self-hosting/deployment-options/azure-app-services/aas-app-service-overview.png) - - - - - - Yes, here are a few that come to mind: - - In step 1.3, we recommend pinning the Docker image to a specific [version of Infisical](https://hub.docker.com/r/infisical/infisical/tags) - instead of referring to the `latest` tag to avoid any unexpected version-to-version migration issues. - - In step 1.2, we recommend selecting a **Region** option that is closest to your infrastructure/clients to reduce latency. - - We're working on putting together a fuller list of deployment best practices as well as minimum resource configuration requirements for running Infisical so stay tuned! - - \ No newline at end of file diff --git a/docs/self-hosting/deployment-options/azure-container-instances.mdx b/docs/self-hosting/deployment-options/azure-container-instances.mdx deleted file mode 100644 index 05e877f37..000000000 --- a/docs/self-hosting/deployment-options/azure-container-instances.mdx +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: "Azure Container Instances" -description: "Deploy Infisical with Azure Container Instances" ---- - -Prerequisites: -- Have an account with [Microsoft Azure](https://azure.microsoft.com/en-us) - - - This brief goes over how to deploy an instance of Infisical with Azure Container Instances without TLS/SSL configuration. - - There are various options for enabling TLS/SSL with Azure Container Instances more suitable for production including: - - [Enabling a TLS endpoint in a sidecar container](https://learn.microsoft.com/en-us/azure/container-instances/container-instances-container-group-ssl). - - [Enabling automatic HTTPS with Caddy in a sidecar container](https://learn.microsoft.com/en-us/azure/container-instances/container-instances-container-group-automatic-ssl). - - Using Azure Function Proxies, Application Gateway, etc. - - For a simpler deployment experience with complete TLS/SSL setup, you may try [deploying Infisical with Azure App Services](/self-hosting/deployment-options/azure-app-services). - - - - - 1.1. In Azure, navigate to the **Container Instances** solution and press **Create**. - - ![Azure container instance](/images/self-hosting/deployment-options/azure-container-instances/aci-select-container-instances.png) - - ![Azure create container instance](/images/self-hosting/deployment-options/azure-container-instances/aci-create-container-instance.png) - - 1.2. In the **Basics** section, specify the **Subscription** and **Resource group** to manage the deployed resource. - - Also, give the container a friendly name like Infisical and specify a **Region** for it to be deployed to. - - ![Azure container instance basics](/images/self-hosting/deployment-options/azure-container-instances/aci-create-container-instance-basics-1.png) - - Next, select the **Public** option under **Image type** and fill in your intended [Infisical public Docker image](https://hub.docker.com/r/infisical/infisical) in the **Image** field; this will pull the image from Docker Hub. - - For example, in order to opt for Infisical `v0.43.4`, you would input: `infisical/infisical:v0.43.4`. - - ![Azure container instance basics](/images/self-hosting/deployment-options/azure-container-instances/aci-create-container-instance-basics-2.png) - - - Depending on your use-case and requirements, you may find it helpful to further configure your Azure container instance. - - For example, you may want to adjust the **Region** option to specify which region to deploy the container for your - instance of Infisical to minimize distance and therefore latency between the instance and your infrastructure. - - - 1.3. In the **Networking** section, select the **Public** option under **Networking type**; this will make the container accessible over the public internet. - - Next, under the **Ports** section, add an entry for port `8080` and protocol `TCP` since Infisical listens on port `8080`. - - ![Azure container instance networking](/images/self-hosting/deployment-options/azure-container-instances/aci-create-container-instance-networking.png) - - 1.4. Running Infisical requires a few environment variables to be set for the Azure container instance. - At minimum, Infisical requires that you set the variables `ENCRYPTION_KEY`, `AUTH_SECRET`, `MONGO_URL`, and `REDIS_URL` - which you can read more about [here](/self-hosting/configuration/envars). - - In the **Advanced** section, fill in the required environment variables. - - - To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars). - - - ![Azure container instance advanced](/images/self-hosting/deployment-options/azure-container-instances/aci-create-container-instance-advanced.png) - - 1.5. Finally, in the **Review + create** section, double check the information from the previous steps and press **Create** to create the Azure container instance. - - ![Azure container instance review](/images/self-hosting/deployment-options/azure-container-instances/aci-create-container-instance-review.png) - - - Head to the **Overview** page of the newly-created container instance to view its **IP address (Public)**; you can access your instance of Infisical by this IP address under the port `:8080`. - - For example, in the image below, the IP address of the sample deployed container instance is `4.255.87.109`; the instance would be accessible in the browser by heading to `4.255.87.109:8080`. - - ![Azure container instance overview](/images/self-hosting/deployment-options/azure-container-instances/aci-container-instance-overview.png) - - - - - - Yes, here are a few that come to mind: - - In step 1.2, we recommend pinning the Docker image to a specific [version of Infisical](https://hub.docker.com/r/infisical/infisical/tags) - instead of referring to the `latest` tag to avoid any unexpected version-to-version migration issues. - - In step 1.2, we recommend selecting a **Region** option that is closest to your infrastructure/clients to reduce latency. - - Enable TLS/SSL with Azure Container Instances. There are various options for doing so including [enabling a TLS endpoint in a sidecar container](https://learn.microsoft.com/en-us/azure/container-instances/container-instances-container-group-ssl), [enabling automatic HTTPS with Caddy in a sidecar container](https://learn.microsoft.com/en-us/azure/container-instances/container-instances-container-group-automatic-ssl), and using Azure Function Proxies, Application Gateway, etc. - - We're working on putting together a fuller list of deployment best practices as well as minimum resource configuration requirements for running Infisical so stay tuned! - - \ No newline at end of file diff --git a/docs/self-hosting/deployment-options/digital-ocean-marketplace.mdx b/docs/self-hosting/deployment-options/digital-ocean-marketplace.mdx deleted file mode 100644 index f1e739f08..000000000 --- a/docs/self-hosting/deployment-options/digital-ocean-marketplace.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Digital Ocean" -description: "Learn to install Infisical on Digital Ocean" ---- - -Infisical can be deployed on a Kubernetes cluster with a single click through our Digital Ocean marketplace application. -The initiation of the installation process triggers the creation of a Kubernetes cluster, followed by the installation of Infisical onto that cluster. - -This automated deployment method uses the same process under the hood as the manual [Kubernetes installation guide](./kubernetes-helm). - -### Initiate the installation - -To start the process, click the following button and follow the instructions there. - - - - - -### Access Infisical Web -Once the installation finishes, head to the `Networking` section via the sidebar and select `Load Balancers`. -Within this section, you'll find the newly created load balancer for Infisical. You can access Infisical at the IP address allocated to that load balancer. - -### Adjusting configurations -If you need to either upgrade or downgrade Infisical, or modify environment variables to alter its functionality, refer to our [Kubernetes installation](./kubernetes-helm) page for detailed instructions. - -Because Digital Ocean deploys the same Helm application as described in our [Kubernetes installation](./kubernetes-helm) guide, you can utilize that guide to implement the required changes. -It's important to note that any modifications requires familiarly with Helm package manager. diff --git a/docs/self-hosting/deployment-options/fly.io.mdx b/docs/self-hosting/deployment-options/fly.io.mdx deleted file mode 100644 index dacd9476b..000000000 --- a/docs/self-hosting/deployment-options/fly.io.mdx +++ /dev/null @@ -1,108 +0,0 @@ ---- -title: "Fly.io" -description: "Deploy Infisical with Fly.io" ---- - -Prerequisites: -- Have an account with [Fly.io](https://fly.io/) -- Have installed the [Fly.io CLI](https://fly.io/docs/hands-on/install-flyctl/) - - - - In your terminal, run the following command from the source directory of your project to create a new Fly.io app - with a `fly.toml` configuration file: - - ``` - fly launch - ``` - - - Add a **build** section to the `fly.toml` file to specify the [Infisical public Docker image](https://hub.docker.com/r/infisical/infisical): - - ``` - [build] - image = "infisical/infisical:v0.43.4" - ``` - - Afterwards, your `fly.toml` file should look similar to: - - ``` - app = "infisical" - primary_region = "lax" - - [http_service] - internal_port = 8080 - force_https = true - auto_stop_machines = true - auto_start_machines = true - min_machines_running = 0 - processes = ["app"] - - [[vm]] - cpu_kind = "shared" - cpus = 1 - memory_mb = 1024 - - [build] - image = "infisical/infisical:v0.43.4" - ``` - - - Depending on your use-case and requirements, you may find it helpful to further configure your `fly.toml` file - with options [here](https://fly.io/docs/reference/configuration/). - - For example, you may want to adjust the `primary-region` option to specify which [region](https://fly.io/docs/reference/regions/) to create the new machine for your - instance of Infisical to minimize distance and therefore latency between the instance and your infrastructure. - - - - - Running Infisical requires a few environment variables to be set on the Fly.io machine. - At minimum, Infisical requires that you set the variables `ENCRYPTION_KEY`, `AUTH_SECRET`, `MONGO_URL`, and `REDIS_URL` - which you can read more about [here](/self-hosting/configuration/envars). - - For this step, we recommend setting the variables as Fly.io [app secrets](https://fly.io/docs/reference/secrets/) which - are made available to the app as environment variables. You can set the variables either via the Fly.io CLI or project [dashboard](https://fly.io/dashboard). - - - - Run the following command (with each `VALUE` replaced) in the source directory of your project to set the required variables: - - ``` - flyctl secrets set ENCRYPTION_KEY=VALUE AUTH_SECRET=VALUE MONGO_URL=VALUE REDIS_URL=VALUE... - ``` - - - In Fly.io, head to your Project > Secrets and add the required variables. - - ![Fly.io deployment secrets](/images/self-hosting/deployment-options/flyio/flyio-secrets.png) - - - - - To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars). - - - - Finally, run the following command in the source directory of your project to deploy your Infisical instance on Fly.io - with the updated `fly.toml` configuration file from step 2 and secrets from step 3: - - ``` - fly deploy - ``` - - - - - - Yes, here are a few that come to mind: - - In step 2, we recommend pinning the Docker image to a specific [version of Infisical](https://hub.docker.com/r/infisical/infisical/tags) - instead of referring to the `latest` tag to avoid any unexpected version-to-version migration issues. - - In step 2, we recommend selecting a `primary_region` option that is closest to your infrastructure/clients to reduce latency; a full list of regions supported by Fly.io can be found [here](https://fly.io/docs/reference/regions/). - - We're working on putting together a fuller list of deployment best practices as well as minimum resource configuration requirements for running Infisical so stay tuned! - - - -Resources: -- [Fly.io documentation](https://fly.io/docs/) \ No newline at end of file diff --git a/docs/self-hosting/deployment-options/gcp-cloud-run.mdx b/docs/self-hosting/deployment-options/gcp-cloud-run.mdx deleted file mode 100644 index 67c9fcf57..000000000 --- a/docs/self-hosting/deployment-options/gcp-cloud-run.mdx +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "GCP Cloud Run" -description: "Deploy Infisical with GCP Cloud Run" ---- - -Prerequisites: -- Have an account with [Google Cloud Platform (GCP)](https://cloud.google.com/) - - - - In GCP, create a new project and give it a friendly name like Infisical. - - ![GCP create project](/images/self-hosting/deployment-options/gcp-cloud-run/gcp-cloud-run-create-project.png) - - ![GCP create project](/images/self-hosting/deployment-options/gcp-cloud-run/gcp-cloud-run-create-project-2.png) - - - 2.1. Inside the GCP project, navigate to the **Cloud Run** product and create a new service. - - ![GCP Cloud Run](/images/self-hosting/deployment-options/gcp-cloud-run/gcp-cloud-run-select-cloud-run.png) - - ![GCP Cloud Run create service](/images/self-hosting/deployment-options/gcp-cloud-run/gcp-cloud-run-create-service.png) - - 2.2. In the service creation form, select the **Deploy one revision from an existing container image** option and fill in your intended [Infisical public Docker image](https://hub.docker.com/r/infisical/infisical) in the container image URL. - - For example, in order to opt for Infisical `v0.43.4`, you would input: `docker.io/infisical/infisical:v0.43.4`. - - ![GCP Cloud Run create service docker image specification](/images/self-hosting/deployment-options/gcp-cloud-run/gcp-cloud-run-create-service-docker-image.png) - - 2.3. Running Infisical requires a few environment variables to be set for the GCP Cloud Run service. - At minimum, Infisical requires that you set the variables `ENCRYPTION_KEY`, `AUTH_SECRET`, `MONGO_URL`, and `REDIS_URL` - which you can read more about [here](/self-hosting/configuration/envars). - - For this step, fill in the required environment variables in the Edit Container > Variables & Secrets > Environment variables section. - - - To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars). - - - ![GCP Cloud Run create service environment variable specification](/images/self-hosting/deployment-options/gcp-cloud-run/gcp-cloud-run-create-service-envars.png) - - - Depending on your use-case and requirements, you may find it helpful to further configure your GCP Cloud Run service. - - For example, you may want to adjust the **Region** option to specify which region to deploy the underlying container for your - instance of Infisical to minimize distance and therefore latency between the instance and your infrastructure. - - - Finally, press **Create** to finish setting up the GCP Cloud Run service. - - - Head to the **Service details** of the newly-created service to view its URL; you can access your instance of Infisical by clicking on the URL. - - ![GCP Cloud Run service details](/images/self-hosting/deployment-options/gcp-cloud-run/gcp-cloud-run-service-details.png) - - - - - - Yes, here are a few that come to mind: - - In step 2, we recommend pinning the Docker image to a specific [version of Infisical](https://hub.docker.com/r/infisical/infisical/tags) - instead of referring to the `latest` tag to avoid any unexpected version-to-version migration issues. - - In step 2, we recommend selecting a **Region** option that is closest to your infrastructure/clients to reduce latency. - - We're working on putting together a fuller list of deployment best practices as well as minimum resource configuration requirements for running Infisical so stay tuned! - - \ No newline at end of file diff --git a/docs/self-hosting/deployment-options/railway.mdx b/docs/self-hosting/deployment-options/railway.mdx deleted file mode 100644 index 29d2ce293..000000000 --- a/docs/self-hosting/deployment-options/railway.mdx +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: "Railway" -description: "Deploy Infisical with Railway" ---- - -Prerequisites: -- Have an account with [Railway](https://railway.app/) - - - - 1.1. In Railway, create a new project and select **Deploy a template > Infisical**. - - ![Railway create project](/images/self-hosting/deployment-options/railway/railway-create-project.png) - - ![Railway deploy template](/images/self-hosting/deployment-options/railway/railway-deploy-template.png) - - ![Railway deploy template infisical](/images/self-hosting/deployment-options/railway/railway-deploy-template-infisical.png) - - ![Railway template overview](/images/self-hosting/deployment-options/railway/railway-template-overview.png) - - 1.2. At minimum, Infisical requires that you set the variables `ENCRYPTION_KEY`, `AUTH_SECRET`, `MONGO_URL`, and `REDIS_URL` - which you can read more about [here](/self-hosting/configuration/envars). - - By default, the Infisical template on Railway pre-configures environment variables on each service in the deployment but requires you to supply two for the Redis and MongoDB services. - - On the MongoDB service, supply a value for the `MONGO_INITDB_ROOT_PASSWORD` variable. - - ![Railway template MongoDB configuration](/images/self-hosting/deployment-options/railway/railway-template-mongodb.png) - - On the Redis service, supply a value for the `REDIS_PASSWORD` variable. - - ![Railway template Redis configuration](/images/self-hosting/deployment-options/railway/railway-template-redis.png) - - ![Railway template Redis configuration](/images/self-hosting/deployment-options/railway/railway-template-redis.png) - - - To use more features like emailing and single sign-on, you can set additional configuration options on the Infisical service [here](/self-hosting/configuration/envars). - - - Finally, press **Deploy** to create the project and deploy the services within it. - - ![Railway template Infisical configuration](/images/self-hosting/deployment-options/railway/railway-template-infisical.png) - - ![Railway Infisical architecture](/images/self-hosting/deployment-options/railway/railway-infisical-architecture.png) - - - Head to the newly-created Infisical service to view its URL under Networking > Public Networking; you can access your instance of Infisical by clicking on the URL. - - ![Railway Infisical service](/images/self-hosting/deployment-options/railway/railway-infisical-service.png) - - - - - - Yes, here are a few that come to mind: - - While the Infisical template on Railway uses the `latest` tag to get the latest version of Infisical, we recommend creating a Railway deployment that pins the Docker image to a specific [version of Infisical](https://hub.docker.com/r/infisical/infisical/tags) to avoid any unexpected version-to-version migration issues. - - We recommend selecting **Deployment region** options for your Railway service deployments to be closest to your infrastructure/clients to reduce latency. - - We're working on putting together a fuller list of deployment best practices as well as minimum resource configuration requirements for running Infisical so stay tuned! - - \ No newline at end of file diff --git a/docs/self-hosting/deployment-options/render.mdx b/docs/self-hosting/deployment-options/render.mdx deleted file mode 100644 index 17faf060a..000000000 --- a/docs/self-hosting/deployment-options/render.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: "Render.com" -description: "Learn to install Infisical Render.com" ---- - -**Prerequisites** -- An account at Render.com -- A document DB instance - -Deploying on Render is one of the quickest ways to have Infisical running in production. -Before you start deployment, you will need to obtain document db connection string. This will be used for `MONGO_URL` environment variable required during installation. - -You can create a document db database using services such as [MongoDB](https://www.mongodb.com/), [AWS DocumentDB](https://aws.amazon.com/documentdb/), and others. Once done, click the link below to start deployment. - -### **[Deploy to Render](https://render.com/deploy?repo=https://github.com/Infisical/infisical)** - -# - - -Once installation is complete, you will have to create the first account. No default account is provided. - \ No newline at end of file diff --git a/docs/self-hosting/reference-architectures/on-premise.mdx b/docs/self-hosting/reference-architectures/on-premise.mdx new file mode 100644 index 000000000..543e9e938 --- /dev/null +++ b/docs/self-hosting/reference-architectures/on-premise.mdx @@ -0,0 +1,70 @@ +--- +title: "On-premise" +description: "Reference architecture for self-hosting Infisical on premise" +--- + +Deploying Infisical on-premise with high availability requires deep knowledge in areas like networking, container orchestration, and database management. +This guide presents a reference architecture that outlines how to achieve such a deployment effectively. +For organizations that do not have the necessary resources or expertise, we recommend opting for managed, dedicated Infisical instances or engaging professional services to mitigate the complexities. + +## System Overview +![On premise architecture](/images/self-hosting/reference-architectures/on-premise-architecture.png) + +The architecture above utilizes a combination of Kubernetes for orchestrating stateless components and virtual machines (VMs) or bare metal for stateful components. +The infrastructure spans multiple data centers for redundancy and load distribution, enhancing availability and disaster recovery capabilities. +You may duplicate the architecture in multiple data centers and join them via Consul to increase availability. This way, if one data center is out of order, active data centers will take over workloads. + +### Stateful vs stateless workloads + +To reduce the challenges of managing state within Kubernetes, including storage provisioning, persistent volume management, and intricate data backup and recovery processes, we strongly recommend deploying stateful components on Virtual Machines (VMs) or bare metal. +As depicted in the architecture, Infisical is intentionally deployed on Kubernetes to leverage its strengths in managing stateless applications. +Being stateless, Infisical fully benefits from Kubernetes' features like horizontal scaling, self-healing, and rolling updates and rollbacks. + +## Core Components + +### Kubernetes Cluster +Infisical is deployed on a Kubernetes cluster, which allows for container management, auto-scaling, and self-healing capabilities. +A load balancer sits in front of the Kubernetes cluster, directing traffic and ensuring even load distribution across the application nodes. +This is the entry point where all other services will interact with Infisical. + + +### Consul as the Networking Backbone +Consul is an critical component in the reference architecture, serving as a unified service networking layer that links and controls services across different environments and data centers. +It functions as the common communication channel between data centers for stateless applications on Kubernetes and stateful services such as databases on dedicated VMs or bare metal. + + +### Postgres with Patroni +The database layer is powered by Postgres, with [Patroni](https://patroni.readthedocs.io/en/latest/) providing automated management to create a high availability setup. Patroni leverages Consul for several critical operations: + +- **Redundancy:** By managing a cluster of one primary and multiple secondary Postgres nodes, the architecture ensures redundancy. +The primary node handles all the write operations, and secondary nodes handle read operations and are prepared to step up in case of primary failure. + +- **Failover and Service Discovery:** Consul is integrated with Patroni for service discovery and health checks. +When Patroni detects that the primary node is unhealthy, it uses Consul to elect a new primary node from the secondaries, thereby ensuring that the database service remains available. + +- **Data Center Awareness:** Patroni configured with Consul is aware of the multi-data center setup and can handle failover across data centers if necessary, which further enhances the system's availability. + +### Redis with Redis Sentinel +For caching and message brokering: + +- Redis is deployed with a primary-replica setup. +- Redis Sentinel monitors the Redis nodes, providing automatic failover and service discovery. +- Write operations go to the primary node, and replicas serve read operations, ensuring data integrity and availability. + +## Multi data center deployment +Infisical can be deployed across a number of data centers to both increase performance and resiliency to disaster scenarios. +For mission critical deployment of Infisical, we recommend deploying Infisical on at least 3 data centers to reduce downtime in the event of complete data center malfunction. + +### Data Center A +Data Center A houses the primary nodes of both Postgres and Redis, which handle all write operations. The secondary nodes and replicas serve as hot standbys for failover. Consul servers maintain the state of the cluster, elect a leader, and facilitate service discovery. + +### $n^{th}$ data center +The $n^{th}$ data center acts as a performance and disaster recovery site, featuring a mesh gateway that enables cross-data center service discovery and configuration. It houses additional secondary nodes for Postgres and Redis replicas, which are ready to be promoted in case the primary data center fails. Additionally, this data center can reduce the latency of applications that need to interact with Infisical, particularly if those applications or services are geographically closer to this data center. + +## Considerations + +The complexity of an on-premise deployment scales with the level of availability required. This reference architecture provides a robust framework for organizations aiming for high availability and disaster resilience. However, it's important to recognize that this is not a one-size-fits-all solution. + +Organizations with less stringent Recovery Time Objectives (RTO) might find that [simpler deployments methods](/self-hosting/deployment-options/docker-compose) using tools such as Docker Compose are adequate. Such setups can still provide a reasonable level of service continuity without the complexities involved in managing a multi-data center environment with Kubernetes, Consul, and other high-availability components. + +Ultimately, the choice of architecture should be guided by a thorough analysis of business needs, available resources, and expertise. \ No newline at end of file diff --git a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx index 03e4f50f6..692bdcbe3 100644 --- a/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx +++ b/frontend/src/views/SecretOverviewPage/SecretOverviewPage.tsx @@ -138,6 +138,7 @@ export const SecretOverviewPage = () => { const { isImportedSecretPresentInEnv } = useGetImportedSecretsAllEnvs({ projectId: workspaceId, decryptFileKey: latestFileKey!, + path: secretPath, environments: userAvailableEnvs.map(({ slug }) => slug) }); @@ -340,6 +341,8 @@ export const SecretOverviewPage = () => { ); const canViewOverviewPage = Boolean(userAvailableEnvs.length); + // This is needed to also show imports from other paths – right now those are missing. + // const combinedKeys = [...secKeys, ...secretImports.map((impSecrets) => impSecrets?.data?.map((impSec) => impSec.secrets?.map((impSecKey) => impSecKey.key))).flat().flat()]; const filteredSecretNames = secKeys ?.filter((name) => name.toUpperCase().includes(searchFilter.toUpperCase())) .sort((a, b) => (sortDir === "asc" ? a.localeCompare(b) : b.localeCompare(a)));