Compare commits

..

83 Commits

Author SHA1 Message Date
0111ee9efb Merge pull request #1700 from akhilmhdh/feat/cli-template
feat(cli): added template feature to cli export command
2024-04-18 15:46:33 -04:00
581ffc613c add go lang add/minus functions and give better example 2024-04-18 15:45:20 -04:00
2aea73861b feat(cli): added template feature to cli export command 2024-04-18 13:09:09 +05:30
4b463c6fde Merge pull request #1698 from Infisical/imported-secret-icon
fixed import icon in the overview dashboard
2024-04-17 15:05:14 -04:00
e6823c520e fixed import icon in the overview dashboard 2024-04-17 12:50:14 -06:00
e973a62753 Merge pull request #1684 from akhilmhdh/chore/drop-role-field
chore: rolling migration removed role and roleId field from project membership and identity project membership
2024-04-17 11:41:59 -04:00
94fa294455 Update email-password.mdx 2024-04-17 08:13:05 -06:00
be63e538d7 Update email-password.mdx 2024-04-17 08:11:49 -06:00
02e423f52c remove old deployment options 2024-04-17 01:16:47 -04:00
3cb226908b Merge pull request #1693 from Infisical/on-premise-architecure
on prem architecture
2024-04-17 00:56:59 -04:00
ba37b1c083 on prem reference 2024-04-17 00:55:37 -04:00
d23b39abba Merge pull request #1692 from Infisical/daniel/cli-fix
Hotfix: CLI run command null pointer reference crash
2024-04-16 14:33:24 -04:00
de92ba157a Update run.go 2024-04-16 20:30:03 +02:00
dadea751e3 Merge pull request #1685 from Infisical/snyk-fix-f3ea1a09d48832703f1fbc7b1eb0a4a3
[Snyk] Security upgrade mysql2 from 3.9.1 to 3.9.4
2024-04-16 13:44:26 -04:00
0ff0357a7c Merge pull request #1630 from Infisical/daniel/cli-ua-support
Feat: Machine Identity support for CLI commands
2024-04-16 13:28:00 -04:00
85f257b4db add tip to only print token 2024-04-16 13:24:39 -04:00
18d7a14e3f add silent flag 2024-04-16 13:21:05 -04:00
ff4d932631 Update aws-amplify.mdx 2024-04-16 10:27:09 -06:00
519f0025c0 Update aws-amplify.mdx 2024-04-16 11:31:18 -04:00
d8d6d7dc1b Merge pull request #1687 from akhilmhdh/docs/aws-amplify-integration
docs: added aws amplify integration documentation
2024-04-16 11:21:38 -04:00
a975fbd8a4 Fix: Moved comments 2024-04-16 10:16:23 +02:00
3a6ec3717b Fix: Use constant identifiers 2024-04-16 10:15:07 +02:00
a4a961996b Fix: Formatting and plain token output 2024-04-16 10:14:41 +02:00
5b4777c1a5 docs: updated cli build command and image on env console 2024-04-16 12:06:09 +05:30
2f526850d6 edits to aws amplify docs 2024-04-16 01:20:52 -04:00
4f5d31d06f edit aws amplify docs 2024-04-16 00:56:39 -04:00
a8264b17e4 Merge pull request #1689 from akhilmhdh/dynamic-secret/mysql
Dynamic secret mysql support
2024-04-15 15:51:17 -04:00
cb66733e6d remove password expire 2024-04-15 15:47:56 -04:00
40a0691ccb feat(ui): added mysql dynamic secret 2024-04-15 19:35:29 +05:30
6410d51033 feat(server): added mysql dynamic secret server logic 2024-04-15 19:34:47 +05:30
bc30ba9ad1 docs: added aws amplify integration documentation 2024-04-15 14:59:44 +05:30
a0259712df Update aws-ecs.mdx 2024-04-15 03:36:12 -04:00
1132d07dea Merge pull request #1686 from Infisical/aws-reference-guide-ecs
AWS ECS reference architecture
2024-04-15 03:24:26 -04:00
1f0b1964b9 ecs reference architecture 2024-04-15 03:23:58 -04:00
690e72b44c fix: backend/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-MYSQL2-6591085
2024-04-14 23:08:40 +00:00
e2967f5e61 chore: added volume mount to migration docker dev image 2024-04-15 02:24:14 +05:30
97afc4ff51 feat: added back username field in response for project users 2024-04-15 02:23:36 +05:30
c47a91715f chore: rolling migration removed role and roleId field from project membership and identity project membership 2024-04-15 02:16:11 +05:30
fbc7b34786 Merge pull request #1683 from akhilmhdh/fix/audit-log-latency
fix: resolved slow audit log list
2024-04-14 11:54:11 -04:00
9e6641c058 fix: resolved slow audit log list 2024-04-14 18:38:42 +05:30
d035403af1 Update kubernetes.mdx 2024-04-12 14:07:31 -04:00
1af0d958dd Update migration order for group 2024-04-12 10:50:06 -07:00
66a51658d7 Merge pull request #1682 from Infisical/k8s-owner-policy
add docs for owner policy
2024-04-12 12:52:09 -04:00
28dc3a4b1c add docs for owner policy 2024-04-12 12:49:45 -04:00
b27cadb651 Merge pull request #1638 from Infisical/groups
User Groups
2024-04-12 08:28:10 -07:00
3dca82ad2f Merge pull request #1680 from Infisical/daniel/recursive-max-depth
Fix: Hard limit on recursive secret fetching
2024-04-12 10:38:38 -04:00
1c90df9dd4 add log for secrets depth breakout 2024-04-12 10:34:59 -04:00
e15c9e72c6 allow inetgrations list fetch via UA 2024-04-12 10:06:16 -04:00
71575b1d2e Fix: Secret interpolation not working as intended for fetching secrets by name 2024-04-12 14:05:43 +02:00
51f164c399 Chore: Add debug logs 2024-04-12 13:42:42 +02:00
702cd0d403 Update secret-fns.ts 2024-04-12 13:31:48 +02:00
75267987fc Fix: Add recursive search max depth (20) 2024-04-12 13:28:03 +02:00
d734a3f6f4 Fix: Add hard recursion limit to documentation 2024-04-12 13:15:42 +02:00
cbb749e34a Update list-project-integrations.mdx 2024-04-11 23:52:25 -04:00
0baea4c5fd Draft 2024-04-08 15:18:15 -07:00
dde24d4c71 Merge pull request #1663 from Infisical/daniel/cli-improvements
Feat: CLI Improvements
2024-04-05 18:42:18 -07:00
8f1e662688 Feat: Added include imports to export command 2024-04-05 17:30:30 -07:00
dcbbb67f03 Feat: Added secret interpolation to get secret by name command 2024-04-05 17:30:19 -07:00
c0fb3c905e Docs: UA Auth Docs 2024-04-05 17:10:38 -07:00
18b0766d96 Update folders.go 2024-04-05 15:47:18 -07:00
b423696630 Feat: UA CLI Support 2024-04-05 15:22:48 -07:00
bf60489fde Feat: Added UA support to export command 2024-04-05 15:20:03 -07:00
85ea6d2585 Fix: Cleanup 2024-04-05 15:19:52 -07:00
a97737ab90 Feat: Folder support for Machine Identities 2024-04-05 15:19:44 -07:00
3793858f0a Feat: Export support for Machine Identities 2024-04-05 15:18:09 -07:00
66c48fbff8 Update model.go 2024-04-05 15:16:12 -07:00
b6b040375b Feat: UA CLI Support 2024-04-05 15:13:47 -07:00
9ad5e082e2 Feat: UA CLI support 2024-04-05 15:13:47 -07:00
f1805811aa Feat: Added token renew command 2024-04-05 15:13:47 -07:00
b135258cce Feat: Added UA support to secret commands 2024-04-05 15:13:47 -07:00
a651de53d1 Feat: Added UA support to run command 2024-04-05 15:13:00 -07:00
7d0a535f46 Feat: Added UA login support (defaults to 'user') 2024-04-05 15:12:32 -07:00
c4e3dd84e3 Feat: Added UA support to folder command 2024-04-05 15:12:32 -07:00
9193f13970 Feat: Added UA support to export command 2024-04-05 15:12:32 -07:00
016f22c295 Fix: Cleanup 2024-04-05 15:12:32 -07:00
4d7182c9b1 Fix: Removed unused struct and included secret type on secret response 2024-04-05 15:12:32 -07:00
6ea7b04efa Feat: Folder support for Machine Identities 2024-04-05 15:12:32 -07:00
3981d61853 Feat: Support for Machine Identities auth 2024-04-05 15:12:32 -07:00
3d391b4e2d Feat: Secrets cmd support for Machine Identities 2024-04-05 15:12:32 -07:00
4123177133 Feat: Run cmd support for Machine Identities 2024-04-05 15:09:38 -07:00
4d61188d0f Feat: Folder support for Machine Identities 2024-04-05 15:08:52 -07:00
fa33f35fcd Feat: Export support for Machine Identities 2024-04-05 15:08:52 -07:00
13629223fb Chore: Moved universalAuthLogin function to utils 2024-04-05 15:08:52 -07:00
66 changed files with 1118 additions and 798 deletions

View File

@ -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",

View File

@ -108,7 +108,7 @@
"libsodium-wrappers": "^0.7.13",
"lodash.isequal": "^4.5.0",
"ms": "^2.1.3",
"mysql2": "^3.9.1",
"mysql2": "^3.9.4",
"nanoid": "^5.0.4",
"nodemailer": "^6.9.9",
"ora": "^7.0.1",

View File

@ -0,0 +1,47 @@
import { Knex } from "knex";
import { ProjectMembershipRole, TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
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<void> {
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);
}
});
}

View File

@ -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(),

View File

@ -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<typeof ProjectMembershipsSchema>;

View File

@ -33,8 +33,7 @@ export async function seed(knex: Knex): Promise<void> {
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({

View File

@ -78,8 +78,7 @@ export async function seed(knex: Knex): Promise<void> {
const identityProjectMembership = await knex(TableName.IdentityProjectMembership)
.insert({
identityId: seedData1.machineIdentity.id,
projectId: seedData1.project.id,
role: ProjectMembershipRole.Admin
projectId: seedData1.project.id
})
.returning("*");

View File

@ -3,7 +3,7 @@ import { z } from "zod";
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -145,6 +145,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actorAuthMethod: req.permission.authMethod,
projectId: req.params.workspaceId,
...req.query,
startDate: req.query.endDate || getLastMidnightDateISO(),
auditLogActor: req.query.actor,
actor: req.permission.type
});

View File

@ -249,7 +249,7 @@ export const dynamicSecretLeaseServiceFactory = ({
if ((revokeResponse as { error?: Error })?.error) {
const { error } = revokeResponse as { error?: Error };
logger.error("Failed to revoke lease", { error: error?.message });
logger.error(error?.message, "Failed to revoke lease");
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
status: DynamicSecretLeaseStatus.FailedDeletion,
statusDetails: error?.message?.slice(0, 255)

View File

@ -1,7 +1,8 @@
import { z } from "zod";
export enum SqlProviders {
Postgres = "postgres"
Postgres = "postgres",
MySQL = "mysql2"
}
export const DynamicSecretSqlDBSchema = z.object({
@ -13,7 +14,7 @@ export const DynamicSecretSqlDBSchema = z.object({
password: z.string(),
creationStatement: z.string(),
revocationStatement: z.string(),
renewStatement: z.string(),
renewStatement: z.string().optional(),
ca: z.string().optional()
});

View File

@ -48,10 +48,10 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
host: providerInputs.host,
user: providerInputs.username,
password: providerInputs.password,
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
ssl,
pool: { min: 0, max: 1 }
}
},
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
});
return db;
};
@ -73,15 +73,25 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
const username = alphaNumericNanoId(32);
const password = generatePassword();
const { database } = providerInputs;
const expiration = new Date(expireAt).toISOString();
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
username,
password,
expiration
expiration,
database
});
await db.raw(creationStatement.toString());
await db.transaction(async (tx) =>
Promise.all(
creationStatement
.toString()
.split(";")
.filter(Boolean)
.map((query) => tx.raw(query))
)
);
await db.destroy();
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
};
@ -91,9 +101,18 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
const db = await getClient(providerInputs);
const username = entityId;
const { database } = providerInputs;
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username });
await db.raw(revokeStatement);
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username, database });
await db.transaction(async (tx) =>
Promise.all(
revokeStatement
.toString()
.split(";")
.filter(Boolean)
.map((query) => tx.raw(query))
)
);
await db.destroy();
return { entityId: username };
@ -105,9 +124,19 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
const username = entityId;
const expiration = new Date(expireAt).toISOString();
const { database } = providerInputs;
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration });
await db.raw(renewStatement);
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration, database });
if (renewStatement)
await db.transaction(async (tx) =>
Promise.all(
renewStatement
.toString()
.split(";")
.filter(Boolean)
.map((query) => tx.raw(query))
)
);
await db.destroy();
return { entityId: username };

View File

@ -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,

View File

@ -272,7 +272,8 @@ export const SECRETS = {
export const RAW_SECRETS = {
LIST: {
recursive: "Whether or not to fetch all secrets from the specified base path, and all of its subdirectories.",
recursive:
"Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.",
workspaceId: "The ID of the project to list secrets from.",
workspaceSlug: "The slug of the project to list secrets from. This parameter is only usable by machine identities.",
environment: "The slug of the environment to list secrets from.",

View File

@ -0,0 +1,2 @@
export const getLastMidnightDateISO = (last = 1) =>
`${new Date(new Date().setDate(new Date().getDate() - last)).toISOString().slice(0, 10)}T00:00:00Z`;

View File

@ -2,5 +2,6 @@
// Full credits goes to https://github.com/rayapps to those functions
// Code taken to keep in in house and to adjust somethings for our needs
export * from "./array";
export * from "./dates";
export * from "./object";
export * from "./string";

View File

@ -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()
})

View File

@ -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()
})
@ -349,7 +346,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const integrations = await server.services.integration.listIntegrationByProject({
actorId: req.permission.id,

View File

@ -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
);

View File

@ -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
);

View File

@ -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
);

View File

@ -21,6 +21,7 @@ import {
} from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors";
import { groupBy, unique } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
import { getBotKeyFnFactory } from "../project-bot/project-bot-fns";
@ -92,7 +93,8 @@ const buildHierarchy = (folders: TSecretFolders[]): FolderMap => {
const generatePaths = (
map: FolderMap,
parentId: string = "null",
basePath: string = ""
basePath: string = "",
currentDepth: number = 0
): { path: string; folderId: string }[] => {
const children = map[parentId || "null"] || [];
let paths: { path: string; folderId: string }[] = [];
@ -105,13 +107,20 @@ const generatePaths = (
// eslint-disable-next-line no-nested-ternary
const currPath = basePath === "" ? (isRootFolder ? "/" : `/${child.name}`) : `${basePath}/${child.name}`;
// Add the current path
paths.push({
path: currPath,
folderId: child.id
}); // Add the current path
});
// Recursively generate paths for children, passing down the formatted pathh
const childPaths = generatePaths(map, child.id, currPath);
// We make sure that the recursion depth doesn't exceed 20.
// We do this to create "circuit break", basically to ensure that we can't encounter any potential memory leaks.
if (currentDepth >= 20) {
logger.info(`generatePaths: Recursion depth exceeded 20, breaking out of recursion [map=${JSON.stringify(map)}]`);
return;
}
// Recursively generate paths for children, passing down the formatted path
const childPaths = generatePaths(map, child.id, currPath, currentDepth + 1);
paths = paths.concat(
childPaths.map((p) => ({
path: p.path,

View File

@ -528,6 +528,7 @@ type GetRawSecretsV3Request struct {
WorkspaceId string `json:"workspaceId"`
SecretPath string `json:"secretPath"`
IncludeImport bool `json:"include_imports"`
Recursive bool `json:"recursive"`
}
type GetRawSecretsV3Response struct {

View File

@ -381,6 +381,12 @@ func ProcessTemplate(templateId int, templatePath string, data interface{}, acce
funcs := template.FuncMap{
"secret": secretFunction,
"dynamic_secret": dynamicSecretFunction,
"minus": func(a, b int) int {
return a - b
},
"add": func(a, b int) int {
return a + b
},
}
templateName := path.Base(templatePath)
@ -479,7 +485,7 @@ func (tm *AgentManager) GetToken() string {
// Fetches a new access token using client credentials
func (tm *AgentManager) FetchNewAccessToken() error {
clientID := os.Getenv("INFISICAL_UNIVERSAL_AUTH_CLIENT_ID")
clientID := os.Getenv(util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME)
if clientID == "" {
clientIDAsByte, err := ReadFile(tm.clientIdPath)
if err != nil {
@ -509,7 +515,7 @@ func (tm *AgentManager) FetchNewAccessToken() error {
// save as cache in memory
tm.cachedClientSecret = clientSecret
err, loginResponse := universalAuthLogin(clientID, clientSecret)
loginResponse, err := util.UniversalAuthLogin(clientID, clientSecret)
if err != nil {
return err
}
@ -725,20 +731,6 @@ func (tm *AgentManager) MonitorSecretChanges(secretTemplate Template, templateId
}
}
func universalAuthLogin(clientId string, clientSecret string) (error, api.UniversalAuthLoginResponse) {
httpClient := resty.New()
httpClient.SetRetryCount(10000).
SetRetryMaxWaitTime(20 * time.Second).
SetRetryWaitTime(5 * time.Second)
tokenResponse, err := api.CallUniversalAuthLogin(httpClient, api.UniversalAuthLoginRequest{ClientId: clientId, ClientSecret: clientSecret})
if err != nil {
return err, api.UniversalAuthLoginResponse{}
}
return nil, tokenResponse
}
// runCmd represents the run command
var agentCmd = &cobra.Command{
Example: `

View File

@ -7,6 +7,7 @@ import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/Infisical/infisical-merge/packages/models"
@ -44,6 +45,11 @@ var exportCmd = &cobra.Command{
util.HandleError(err)
}
includeImports, err := cmd.Flags().GetBool("include-imports")
if err != nil {
util.HandleError(err)
}
projectId, err := cmd.Flags().GetString("projectId")
if err != nil {
util.HandleError(err)
@ -54,13 +60,17 @@ var exportCmd = &cobra.Command{
util.HandleError(err)
}
templatePath, err := cmd.Flags().GetString("template")
if err != nil {
util.HandleError(err)
}
secretOverriding, err := cmd.Flags().GetBool("secret-overriding")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
infisicalToken, err := util.GetInfisicalServiceToken(cmd)
token, err := util.GetInfisicalToken(cmd)
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
@ -75,7 +85,46 @@ var exportCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, WorkspaceId: projectId, SecretsPath: secretsPath}, "")
request := models.GetAllSecretsParameters{
Environment: environmentName,
TagSlugs: tagSlugs,
WorkspaceId: projectId,
SecretsPath: secretsPath,
IncludeImport: includeImports,
}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
request.UniversalAuthAccessToken = token.Token
}
if templatePath != "" {
sigChan := make(chan os.Signal, 1)
dynamicSecretLeases := NewDynamicSecretLeaseManager(sigChan)
newEtag := ""
accessToken := ""
if token != nil {
accessToken = token.Token
} else {
log.Debug().Msg("GetAllEnvironmentVariables: Trying to fetch secrets using logged in details")
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
if err != nil {
util.HandleError(err)
}
accessToken = loggedInUserDetails.UserCredentials.JTWToken
}
processedTemplate, err := ProcessTemplate(1, templatePath, nil, accessToken, "", &newEtag, dynamicSecretLeases)
if err != nil {
util.HandleError(err)
}
fmt.Print(processedTemplate.String())
return
}
secrets, err := util.GetAllEnvironmentVariables(request, "")
if err != nil {
util.HandleError(err, "Unable to fetch secrets")
}
@ -88,9 +137,16 @@ var exportCmd = &cobra.Command{
var output string
if shouldExpandSecrets {
secrets = util.ExpandSecrets(secrets, models.ExpandSecretsAuthentication{
InfisicalToken: infisicalToken,
}, "")
authParams := models.ExpandSecretsAuthentication{}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
authParams.UniversalAuthAccessToken = token.Token
}
secrets = util.ExpandSecrets(secrets, authParams, "")
}
secrets = util.FilterSecretsByTag(secrets, tagSlugs)
output, err = formatEnvs(secrets, format)
@ -110,10 +166,12 @@ func init() {
exportCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
exportCmd.Flags().StringP("format", "f", "dotenv", "Set the format of the output file (dotenv, json, csv)")
exportCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
exportCmd.Flags().Bool("include-imports", true, "Imported linked secrets")
exportCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
exportCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs")
exportCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets from")
exportCmd.Flags().String("path", "/", "get secrets within a folder path")
exportCmd.Flags().String("template", "", "The path to the template file used to render secrets")
}
// Format according to the format flag

View File

@ -36,18 +36,33 @@ var getCmd = &cobra.Command{
}
}
infisicalToken, err := util.GetInfisicalServiceToken(cmd)
projectId, err := cmd.Flags().GetString("projectId")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
token, err := util.GetInfisicalToken(cmd)
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
foldersPath, err := cmd.Flags().GetString("path")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
folders, err := util.GetAllFolders(models.GetAllFoldersParameters{Environment: environmentName, InfisicalToken: infisicalToken, FoldersPath: foldersPath})
request := models.GetAllFoldersParameters{
Environment: environmentName,
WorkspaceId: projectId,
FoldersPath: foldersPath,
}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
request.UniversalAuthAccessToken = token.Token
}
folders, err := util.GetAllFolders(request)
if err != nil {
util.HandleError(err, "Unable to get folders")
}

View File

@ -55,95 +55,157 @@ var loginCmd = &cobra.Command{
Short: "Login into your Infisical account",
DisableFlagsInUseLine: true,
Run: func(cmd *cobra.Command, args []string) {
currentLoggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
// if the key can't be found or there is an error getting current credentials from key ring, allow them to override
if err != nil && (strings.Contains(err.Error(), "we couldn't find your logged in details")) {
log.Debug().Err(err)
} else if err != nil {
loginMethod, err := cmd.Flags().GetString("method")
if err != nil {
util.HandleError(err)
}
plainOutput, err := cmd.Flags().GetBool("plain")
if err != nil {
util.HandleError(err)
}
if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 {
shouldOverride, err := userLoginMenu(currentLoggedInUserDetails.UserCredentials.Email)
if err != nil {
if loginMethod != "user" && loginMethod != "universal-auth" {
util.PrintErrorMessageAndExit("Invalid login method. Please use either 'user' or 'universal-auth'")
}
if loginMethod == "user" {
currentLoggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
// if the key can't be found or there is an error getting current credentials from key ring, allow them to override
if err != nil && (strings.Contains(err.Error(), "we couldn't find your logged in details")) {
log.Debug().Err(err)
} else if err != nil {
util.HandleError(err)
}
if !shouldOverride {
return
if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 {
shouldOverride, err := userLoginMenu(currentLoggedInUserDetails.UserCredentials.Email)
if err != nil {
util.HandleError(err)
}
if !shouldOverride {
return
}
}
}
//override domain
domainQuery := true
if config.INFISICAL_URL_MANUAL_OVERRIDE != "" && config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
overrideDomain, err := DomainOverridePrompt()
if err != nil {
util.HandleError(err)
//override domain
domainQuery := true
if config.INFISICAL_URL_MANUAL_OVERRIDE != "" && config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
overrideDomain, err := DomainOverridePrompt()
if err != nil {
util.HandleError(err)
}
//if not override set INFISICAL_URL to exported var
//set domainQuery to false
if !overrideDomain {
domainQuery = false
config.INFISICAL_URL = config.INFISICAL_URL_MANUAL_OVERRIDE
}
}
//if not override set INFISICAL_URL to exported var
//set domainQuery to false
if !overrideDomain {
domainQuery = false
config.INFISICAL_URL = config.INFISICAL_URL_MANUAL_OVERRIDE
//prompt user to select domain between Infisical cloud and self hosting
if domainQuery {
err = askForDomain()
if err != nil {
util.HandleError(err, "Unable to parse domain url")
}
}
var userCredentialsToBeStored models.UserCredentials
}
//prompt user to select domain between Infisical cloud and self hosting
if domainQuery {
err = askForDomain()
if err != nil {
util.HandleError(err, "Unable to parse domain url")
}
}
var userCredentialsToBeStored models.UserCredentials
interactiveLogin := false
if cmd.Flags().Changed("interactive") {
interactiveLogin = true
cliDefaultLogin(&userCredentialsToBeStored)
}
//call browser login function
if !interactiveLogin {
fmt.Println("Logging in via browser... To login via interactive mode run [infisical login -i]")
userCredentialsToBeStored, err = browserCliLogin()
if err != nil {
//default to cli login on error
interactiveLogin := false
if cmd.Flags().Changed("interactive") {
interactiveLogin = true
cliDefaultLogin(&userCredentialsToBeStored)
}
//call browser login function
if !interactiveLogin {
fmt.Println("Logging in via browser... To login via interactive mode run [infisical login -i]")
userCredentialsToBeStored, err = browserCliLogin()
if err != nil {
//default to cli login on error
cliDefaultLogin(&userCredentialsToBeStored)
}
}
err = util.StoreUserCredsInKeyRing(&userCredentialsToBeStored)
if err != nil {
log.Error().Msgf("Unable to store your credentials in system vault [%s]")
log.Error().Msgf("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq")
log.Debug().Err(err)
//return here
util.HandleError(err)
}
err = util.WriteInitalConfig(&userCredentialsToBeStored)
if err != nil {
util.HandleError(err, "Unable to write write to Infisical Config file. Please try again")
}
// clear backed up secrets from prev account
util.DeleteBackupSecrets()
whilte := color.New(color.FgGreen)
boldWhite := whilte.Add(color.Bold)
time.Sleep(time.Second * 1)
boldWhite.Printf(">>>> Welcome to Infisical!")
boldWhite.Printf(" You are now logged in as %v <<<< \n", userCredentialsToBeStored.Email)
plainBold := color.New(color.Bold)
plainBold.Println("\nQuick links")
fmt.Println("- Learn to inject secrets into your application at https://infisical.com/docs/cli/usage")
fmt.Println("- Stuck? Join our slack for quick support https://infisical.com/slack")
Telemetry.CaptureEvent("cli-command:login", posthog.NewProperties().Set("infisical-backend", config.INFISICAL_URL).Set("version", util.CLI_VERSION))
} else if loginMethod == "universal-auth" {
clientId, err := cmd.Flags().GetString("client-id")
if err != nil {
util.HandleError(err)
}
clientSecret, err := cmd.Flags().GetString("client-secret")
if err != nil {
util.HandleError(err)
}
if clientId == "" {
clientId = os.Getenv(util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME)
if clientId == "" {
util.PrintErrorMessageAndExit("Please provide client-id")
}
}
if clientSecret == "" {
clientSecret = os.Getenv(util.INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_NAME)
if clientSecret == "" {
util.PrintErrorMessageAndExit("Please provide client-secret")
}
}
res, err := util.UniversalAuthLogin(clientId, clientSecret)
if err != nil {
util.HandleError(err)
}
if plainOutput {
fmt.Println(res.AccessToken)
return
}
boldGreen := color.New(color.FgGreen).Add(color.Bold)
boldPlain := color.New(color.Bold)
time.Sleep(time.Second * 1)
boldGreen.Printf(">>>> Successfully authenticated with Universal Auth!\n\n")
boldPlain.Printf("Universal Auth Access Token:\n%v", res.AccessToken)
plainBold := color.New(color.Bold)
plainBold.Println("\n\nYou can use this access token to authenticate through other commands in the CLI.")
}
err = util.StoreUserCredsInKeyRing(&userCredentialsToBeStored)
if err != nil {
log.Error().Msgf("Unable to store your credentials in system vault [%s]")
log.Error().Msgf("\nTo trouble shoot further, read https://infisical.com/docs/cli/faq")
log.Debug().Err(err)
//return here
util.HandleError(err)
}
err = util.WriteInitalConfig(&userCredentialsToBeStored)
if err != nil {
util.HandleError(err, "Unable to write write to Infisical Config file. Please try again")
}
// clear backed up secrets from prev account
util.DeleteBackupSecrets()
whilte := color.New(color.FgGreen)
boldWhite := whilte.Add(color.Bold)
time.Sleep(time.Second * 1)
boldWhite.Printf(">>>> Welcome to Infisical!")
boldWhite.Printf(" You are now logged in as %v <<<< \n", userCredentialsToBeStored.Email)
plainBold := color.New(color.Bold)
plainBold.Println("\nQuick links")
fmt.Println("- Learn to inject secrets into your application at https://infisical.com/docs/cli/usage")
fmt.Println("- Stuck? Join our slack for quick support https://infisical.com/slack")
Telemetry.CaptureEvent("cli-command:login", posthog.NewProperties().Set("infisical-backend", config.INFISICAL_URL).Set("version", util.CLI_VERSION))
},
}
@ -313,6 +375,10 @@ func cliDefaultLogin(userCredentialsToBeStored *models.UserCredentials) {
func init() {
rootCmd.AddCommand(loginCmd)
loginCmd.Flags().BoolP("interactive", "i", false, "login via the command line")
loginCmd.Flags().String("method", "user", "login method [user, universal-auth]")
loginCmd.Flags().String("client-id", "", "client id for universal auth")
loginCmd.Flags().Bool("plain", false, "only output the token without any formatting")
loginCmd.Flags().String("client-secret", "", "client secret for universal auth")
}
func DomainOverridePrompt() (bool, error) {

View File

@ -40,8 +40,14 @@ func init() {
rootCmd.PersistentFlags().StringP("log-level", "l", "info", "log level (trace, debug, info, warn, error, fatal)")
rootCmd.PersistentFlags().Bool("telemetry", true, "Infisical collects non-sensitive telemetry data to enhance features and improve user experience. Participation is voluntary")
rootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", util.INFISICAL_DEFAULT_API_URL, "Point the CLI to your own backend [can also set via environment variable name: INFISICAL_API_URL]")
rootCmd.PersistentFlags().Bool("silent", false, "Disable output of tip/info messages. Useful when running in scripts or CI/CD pipelines.")
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
if !util.IsRunningInDocker() {
silent, err := cmd.Flags().GetBool("silent")
if err != nil {
util.HandleError(err)
}
if !util.IsRunningInDocker() && !silent {
util.CheckForUpdate()
}
}

View File

@ -62,8 +62,7 @@ var runCmd = &cobra.Command{
}
}
infisicalToken, err := util.GetInfisicalServiceToken(cmd)
token, err := util.GetInfisicalToken(cmd)
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
@ -73,6 +72,11 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
projectId, err := cmd.Flags().GetString("projectId")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
secretOverriding, err := cmd.Flags().GetBool("secret-overriding")
if err != nil {
util.HandleError(err, "Unable to parse flag")
@ -103,7 +107,22 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports, Recursive: recursive}, projectConfigDir)
request := models.GetAllSecretsParameters{
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: includeImports,
Recursive: recursive,
}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
request.UniversalAuthAccessToken = token.Token
}
secrets, err := util.GetAllEnvironmentVariables(request, projectConfigDir)
if err != nil {
util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid")
@ -116,9 +135,16 @@ var runCmd = &cobra.Command{
}
if shouldExpandSecrets {
secrets = util.ExpandSecrets(secrets, models.ExpandSecretsAuthentication{
InfisicalToken: infisicalToken,
}, projectConfigDir)
authParams := models.ExpandSecretsAuthentication{}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
authParams.UniversalAuthAccessToken = token.Token
}
secrets = util.ExpandSecrets(secrets, authParams, projectConfigDir)
}
secretsByKey := getSecretsByKeys(secrets)
@ -149,7 +175,15 @@ var runCmd = &cobra.Command{
log.Debug().Msgf("injecting the following environment variables into shell: %v", env)
Telemetry.CaptureEvent("cli-command:run", posthog.NewProperties().Set("secretsCount", len(secrets)).Set("environment", environmentName).Set("isUsingServiceToken", infisicalToken != "").Set("single-command", strings.Join(args, " ")).Set("multi-command", cmd.Flag("command").Value.String()).Set("version", util.CLI_VERSION))
Telemetry.CaptureEvent("cli-command:run",
posthog.NewProperties().
Set("secretsCount", len(secrets)).
Set("environment", environmentName).
Set("isUsingServiceToken", token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER).
Set("isUsingUniversalAuthToken", token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER).
Set("single-command", strings.Join(args, " ")).
Set("multi-command", cmd.Flag("command").Value.String()).
Set("version", util.CLI_VERSION))
if cmd.Flags().Changed("command") {
command := cmd.Flag("command").Value.String()
@ -204,6 +238,7 @@ func filterReservedEnvVars(env map[string]models.SingleEnvironmentVariable) {
func init() {
rootCmd.AddCommand(runCmd)
runCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
runCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
runCmd.Flags().Bool("include-imports", true, "Import linked secrets ")

View File

@ -38,12 +38,12 @@ var secretsCmd = &cobra.Command{
}
}
infisicalToken, err := util.GetInfisicalServiceToken(cmd)
token, err := util.GetInfisicalToken(cmd)
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
projectId, err := cmd.Flags().GetString("projectId")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
@ -78,7 +78,22 @@ var secretsCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: includeImports, Recursive: recursive}, "")
request := models.GetAllSecretsParameters{
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: includeImports,
Recursive: recursive,
}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
request.UniversalAuthAccessToken = token.Token
}
secrets, err := util.GetAllEnvironmentVariables(request, "")
if err != nil {
util.HandleError(err)
}
@ -90,9 +105,15 @@ var secretsCmd = &cobra.Command{
}
if shouldExpandSecrets {
secrets = util.ExpandSecrets(secrets, models.ExpandSecretsAuthentication{
InfisicalToken: infisicalToken,
}, "")
authParams := models.ExpandSecretsAuthentication{}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
authParams.UniversalAuthAccessToken = token.Token
}
secrets = util.ExpandSecrets(secrets, authParams, "")
}
visualize.PrintAllSecretDetails(secrets)
@ -402,8 +423,12 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
}
}
infisicalToken, err := util.GetInfisicalServiceToken(cmd)
token, err := util.GetInfisicalToken(cmd)
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
shouldExpand, err := cmd.Flags().GetBool("expand")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
@ -413,6 +438,11 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to parse flag")
}
projectId, err := cmd.Flags().GetString("projectId")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
secretsPath, err := cmd.Flags().GetString("path")
if err != nil {
util.HandleError(err, "Unable to parse path flag")
@ -428,11 +458,37 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to parse path flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: true, Recursive: recursive}, "")
request := models.GetAllSecretsParameters{
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: true,
Recursive: recursive,
}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
request.UniversalAuthAccessToken = token.Token
}
secrets, err := util.GetAllEnvironmentVariables(request, "")
if err != nil {
util.HandleError(err, "To fetch all secrets")
}
if shouldExpand {
authParams := models.ExpandSecretsAuthentication{}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
authParams.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
authParams.UniversalAuthAccessToken = token.Token
}
secrets = util.ExpandSecrets(secrets, authParams, "")
}
requestedSecrets := []models.SingleEnvironmentVariable{}
secretsMap := getSecretsByKeys(secrets)
@ -475,8 +531,12 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to parse flag")
}
infisicalToken, err := util.GetInfisicalServiceToken(cmd)
token, err := util.GetInfisicalToken(cmd)
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
projectId, err := cmd.Flags().GetString("projectId")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
@ -486,7 +546,21 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath, IncludeImport: true}, "")
request := models.GetAllSecretsParameters{
Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
IncludeImport: true,
}
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
request.UniversalAuthAccessToken = token.Token
}
secrets, err := util.GetAllEnvironmentVariables(request, "")
if err != nil {
util.HandleError(err, "To fetch all secrets")
}
@ -686,19 +760,23 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
func init() {
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
secretsGenerateExampleEnvCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
secretsGenerateExampleEnvCmd.Flags().String("path", "/", "Fetch secrets from within a folder path")
secretsCmd.AddCommand(secretsGenerateExampleEnvCmd)
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
secretsCmd.AddCommand(secretsGetCmd)
secretsGetCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
secretsGetCmd.Flags().String("path", "/", "get secrets within a folder path")
secretsGetCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
secretsGetCmd.Flags().Bool("raw-value", false, "Returns only the value of secret, only works with one secret")
secretsGetCmd.Flags().Bool("recursive", false, "Fetch secrets from all sub-folders")
secretsCmd.AddCommand(secretsGetCmd)
secretsCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
secretsCmd.AddCommand(secretsSetCmd)
secretsSetCmd.Flags().String("path", "/", "set secrets within a folder path")
// Only supports logged in users (JWT auth)
secretsSetCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
util.RequireLogin()
util.RequireLocalWorkspaceFile()
@ -707,6 +785,8 @@ func init() {
secretsDeleteCmd.Flags().String("type", "personal", "the type of secret to delete: personal or shared (default: personal)")
secretsDeleteCmd.Flags().String("path", "/", "get secrets within a folder path")
secretsCmd.AddCommand(secretsDeleteCmd)
// Only supports logged in users (JWT auth)
secretsDeleteCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
util.RequireLogin()
util.RequireLocalWorkspaceFile()
@ -718,6 +798,7 @@ func init() {
// Add getCmd, createCmd and deleteCmd flags here
getCmd.Flags().StringP("path", "p", "/", "The path from where folders should be fetched from")
getCmd.Flags().String("token", "", "Fetch folders using the infisical token")
getCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
folderCmd.AddCommand(getCmd)
// Add createCmd flags here
@ -735,6 +816,7 @@ func init() {
// ** End of folders sub command
secretsCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
secretsCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
secretsCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
secretsCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
secretsCmd.Flags().Bool("include-imports", true, "Imported linked secrets ")

63
cli/packages/cmd/token.go Normal file
View File

@ -0,0 +1,63 @@
/*
Copyright (c) 2023 Infisical Inc.
*/
package cmd
import (
"strings"
"time"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/fatih/color"
"github.com/spf13/cobra"
)
var tokenCmd = &cobra.Command{
Use: "token",
Short: "Manage your access tokens",
DisableFlagsInUseLine: true,
Example: "infisical token",
Args: cobra.ExactArgs(0),
PreRun: func(cmd *cobra.Command, args []string) {
util.RequireLogin()
},
Run: func(cmd *cobra.Command, args []string) {
},
}
var tokenRenewCmd = &cobra.Command{
Use: "renew [token]",
Short: "Used to renew your universal auth access token",
DisableFlagsInUseLine: true,
Example: "infisical token renew <access-token>",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// args[0] will be the <INSERT_TOKEN> from your command call
token := args[0]
if strings.HasPrefix(token, "st.") {
util.PrintErrorMessageAndExit("You are trying to renew a service token. You can only renew universal auth access tokens.")
}
renewedAccessToken, err := util.RenewUniversalAuthAccessToken(token)
if err != nil {
util.HandleError(err, "Unable to renew token")
}
boldGreen := color.New(color.FgGreen).Add(color.Bold)
time.Sleep(time.Second * 1)
boldGreen.Printf(">>>> Successfully renewed token!\n\n")
boldGreen.Printf("Renewed Access Token:\n%v", renewedAccessToken)
plainBold := color.New(color.Bold)
plainBold.Println("\n\nYou can use the new access token to authenticate through other commands in the CLI.")
},
}
func init() {
tokenCmd.AddCommand(tokenRenewCmd)
rootCmd.AddCommand(tokenCmd)
}

View File

@ -59,6 +59,11 @@ type DynamicSecretLease struct {
Data map[string]interface{} `json:"data"`
}
type TokenDetails struct {
Type string
Token string
}
type SingleFolder struct {
ID string `json:"_id"`
Name string `json:"name"`
@ -97,10 +102,11 @@ type GetAllSecretsParameters struct {
}
type GetAllFoldersParameters struct {
WorkspaceId string
Environment string
FoldersPath string
InfisicalToken string
WorkspaceId string
Environment string
FoldersPath string
InfisicalToken string
UniversalAuthAccessToken string
}
type CreateFolderParameters struct {
@ -123,3 +129,8 @@ type ExpandSecretsAuthentication struct {
InfisicalToken string
UniversalAuthAccessToken string
}
type MachineIdentityCredentials struct {
ClientId string
ClientSecret string
}

View File

@ -1,17 +1,23 @@
package util
const (
CONFIG_FILE_NAME = "infisical-config.json"
CONFIG_FOLDER_NAME = ".infisical"
INFISICAL_DEFAULT_API_URL = "https://app.infisical.com/api"
INFISICAL_DEFAULT_URL = "https://app.infisical.com"
INFISICAL_WORKSPACE_CONFIG_FILE_NAME = ".infisical.json"
INFISICAL_TOKEN_NAME = "INFISICAL_TOKEN"
SECRET_TYPE_PERSONAL = "personal"
SECRET_TYPE_SHARED = "shared"
KEYRING_SERVICE_NAME = "infisical"
PERSONAL_SECRET_TYPE_NAME = "personal"
SHARED_SECRET_TYPE_NAME = "shared"
CONFIG_FILE_NAME = "infisical-config.json"
CONFIG_FOLDER_NAME = ".infisical"
INFISICAL_DEFAULT_API_URL = "https://app.infisical.com/api"
INFISICAL_DEFAULT_URL = "https://app.infisical.com"
INFISICAL_WORKSPACE_CONFIG_FILE_NAME = ".infisical.json"
INFISICAL_TOKEN_NAME = "INFISICAL_TOKEN"
INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME = "INFISICAL_UNIVERSAL_AUTH_CLIENT_ID"
INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_NAME = "INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET"
INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN_NAME = "INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN"
SECRET_TYPE_PERSONAL = "personal"
SECRET_TYPE_SHARED = "shared"
KEYRING_SERVICE_NAME = "infisical"
PERSONAL_SECRET_TYPE_NAME = "personal"
SHARED_SECRET_TYPE_NAME = "shared"
SERVICE_TOKEN_IDENTIFIER = "service-token"
UNIVERSAL_AUTH_TOKEN_IDENTIFIER = "universal-auth-token"
)
var (

View File

@ -19,7 +19,7 @@ func GetAllFolders(params models.GetAllFoldersParameters) ([]models.SingleFolder
var foldersToReturn []models.SingleFolder
var folderErr error
if params.InfisicalToken == "" {
if params.InfisicalToken == "" && params.UniversalAuthAccessToken == "" {
log.Debug().Msg("GetAllFolders: Trying to fetch folders using logged in details")
@ -44,11 +44,24 @@ func GetAllFolders(params models.GetAllFoldersParameters) ([]models.SingleFolder
folders, err := GetFoldersViaJTW(loggedInUserDetails.UserCredentials.JTWToken, workspaceFile.WorkspaceId, params.Environment, params.FoldersPath)
folderErr = err
foldersToReturn = folders
} else {
} else if params.InfisicalToken != "" {
log.Debug().Msg("GetAllFolders: Trying to fetch folders using service token")
// get folders via service token
folders, err := GetFoldersViaServiceToken(params.InfisicalToken, params.WorkspaceId, params.Environment, params.FoldersPath)
folderErr = err
foldersToReturn = folders
} else if params.UniversalAuthAccessToken != "" {
log.Debug().Msg("GetAllFolders: Trying to fetch folders using universal auth")
if params.WorkspaceId == "" {
PrintErrorMessageAndExit("Project ID is required when using machine identity")
}
// get folders via machine identity
folders, err := GetFoldersViaMachineIdentity(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.FoldersPath)
folderErr = err
foldersToReturn = folders
}
return foldersToReturn, folderErr
}
@ -132,6 +145,34 @@ func GetFoldersViaServiceToken(fullServiceToken string, workspaceId string, envi
return folders, nil
}
func GetFoldersViaMachineIdentity(accessToken string, workspaceId string, envSlug string, foldersPath string) ([]models.SingleFolder, error) {
httpClient := resty.New()
httpClient.SetAuthToken(accessToken).
SetHeader("Accept", "application/json")
getFoldersRequest := api.GetFoldersV1Request{
WorkspaceId: workspaceId,
Environment: envSlug,
FoldersPath: foldersPath,
}
apiResponse, err := api.CallGetFoldersV1(httpClient, getFoldersRequest)
if err != nil {
return nil, err
}
var folders []models.SingleFolder
for _, folder := range apiResponse.Folders {
folders = append(folders, models.SingleFolder{
Name: folder.Name,
ID: folder.ID,
})
}
return folders, nil
}
// CreateFolder creates a folder in Infisical
func CreateFolder(params models.CreateFolderParameters) (models.SingleFolder, error) {
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()

View File

@ -9,8 +9,11 @@ import (
"os/exec"
"path"
"strings"
"time"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/go-resty/resty/v2"
"github.com/spf13/cobra"
)
@ -64,18 +67,70 @@ func IsSecretTypeValid(s string) bool {
return false
}
func GetInfisicalServiceToken(cmd *cobra.Command) (serviceToken string, err error) {
func GetInfisicalToken(cmd *cobra.Command) (token *models.TokenDetails, err error) {
infisicalToken, err := cmd.Flags().GetString("token")
if infisicalToken == "" {
infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
if err != nil {
return nil, err
}
if infisicalToken == "" { // If no flag is passed, we first check for the universal auth access token env variable.
infisicalToken = os.Getenv(INFISICAL_UNIVERSAL_AUTH_ACCESS_TOKEN_NAME)
if infisicalToken == "" { // If it's still empty after the first env check, we check for the service token env variable.
infisicalToken = os.Getenv(INFISICAL_TOKEN_NAME)
}
}
if infisicalToken == "" { // If it's empty, we return nothing at all.
return nil, nil
}
if strings.HasPrefix(infisicalToken, "st.") {
return &models.TokenDetails{
Type: SERVICE_TOKEN_IDENTIFIER,
Token: infisicalToken,
}, nil
}
return &models.TokenDetails{
Type: UNIVERSAL_AUTH_TOKEN_IDENTIFIER,
Token: infisicalToken,
}, nil
}
func UniversalAuthLogin(clientId string, clientSecret string) (api.UniversalAuthLoginResponse, error) {
httpClient := resty.New()
httpClient.SetRetryCount(10000).
SetRetryMaxWaitTime(20 * time.Second).
SetRetryWaitTime(5 * time.Second)
tokenResponse, err := api.CallUniversalAuthLogin(httpClient, api.UniversalAuthLoginRequest{ClientId: clientId, ClientSecret: clientSecret})
if err != nil {
return api.UniversalAuthLoginResponse{}, err
}
return tokenResponse, nil
}
func RenewUniversalAuthAccessToken(accessToken string) (string, error) {
httpClient := resty.New()
httpClient.SetRetryCount(10000).
SetRetryMaxWaitTime(20 * time.Second).
SetRetryWaitTime(5 * time.Second)
request := api.UniversalAuthRefreshRequest{
AccessToken: accessToken,
}
tokenResponse, err := api.CallUniversalAuthRefreshAccessToken(httpClient, request)
if err != nil {
return "", err
}
return infisicalToken, nil
return tokenResponse.AccessToken, nil
}
// Checks if the passed in email already exists in the users slice

View File

@ -159,7 +159,7 @@ func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId strin
httpClient.SetAuthToken(accessToken).
SetHeader("Accept", "application/json")
getSecretsRequest := api.GetEncryptedSecretsV3Request{
getSecretsRequest := api.GetRawSecretsV3Request{
WorkspaceId: workspaceId,
Environment: environmentName,
IncludeImport: includeImports,
@ -171,7 +171,8 @@ func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId strin
getSecretsRequest.SecretPath = secretsPath
}
rawSecrets, err := api.CallGetRawSecretsV3(httpClient, api.GetRawSecretsV3Request{WorkspaceId: workspaceId, SecretPath: secretsPath, Environment: environmentName})
rawSecrets, err := api.CallGetRawSecretsV3(httpClient, getSecretsRequest)
if err != nil {
return models.PlaintextSecretResult{}, err
}
@ -182,7 +183,7 @@ func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId strin
}
for _, secret := range rawSecrets.Secrets {
plainTextSecrets = append(plainTextSecrets, models.SingleEnvironmentVariable{Key: secret.SecretKey, Value: secret.SecretValue, WorkspaceId: secret.Workspace})
plainTextSecrets = append(plainTextSecrets, models.SingleEnvironmentVariable{Key: secret.SecretKey, Value: secret.SecretValue, Type: secret.Type, WorkspaceId: secret.Workspace})
}
// if includeImports {
@ -355,6 +356,11 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
log.Debug().Msg("Trying to fetch secrets using service token")
secretsToReturn, _, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
} else if params.UniversalAuthAccessToken != "" {
if params.WorkspaceId == "" {
PrintErrorMessageAndExit("Project ID is required when using machine identity")
}
log.Debug().Msg("Trying to fetch secrets using universal auth")
res, err := GetPlainTextSecretsViaMachineIdentity(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)

View File

@ -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

View File

@ -1,4 +1,4 @@
---
title: "List project integrations "
title: "List Project Integrations"
openapi: "GET /api/v1/workspace/{workspaceId}/integrations"
---

View File

@ -33,6 +33,9 @@ Export environment variables from the platform into a file format.
# Export variables to a YAML file
infisical export --format=yaml > secrets.yaml
# Render secrets using a custom template file
infisical export --template=<path to template>
```
### Environment variables
@ -57,6 +60,26 @@ Export environment variables from the platform into a file format.
</Accordion>
### flags
<Accordion title="--template">
The `--template` flag specifies the path to the template file used for rendering secrets. When using templates, you can omit the other format flags.
```text my-template-file
{{$secrets := secret "<infisical-project-id>" "<environment-slug>" "<folder-path>"}}
{{$length := len $secrets}}
{{- "{"}}
{{- with $secrets }}
{{- range $index, $secret := . }}
"{{ $secret.Key }}": "{{ $secret.Value }}"{{if lt $index (minus $length 1)}},{{end}}
{{- end }}
{{- end }}
{{ "}" -}}
```
```bash
# Example
infisical export --template="/path/to/template/file"
```
</Accordion>
<Accordion title="--env">
Used to set the environment that secrets are pulled from.

View File

@ -12,4 +12,53 @@ The CLI uses authentication to verify your identity. When you enter the correct
To change where the login credentials are stored, visit the [vaults command](./vault).
If you have added multiple users, you can switch between the users by using the [user command](./user).
If you have added multiple users, you can switch between the users by using the [user command](./user).
### Flags
<Accordion title="--method">
```bash
infisical login --method=<auth-method> # Optional, will default to 'user'.
```
#### Valid values for the `method` flag are:
- `user`: Login using email and password.
- `universal-auth`: Login using a universal auth client ID and client secret.
<Info>
When `method` is set to `universal-auth`, the `client-id` and `client-secret` flags are required. Optionally you can set the `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` and `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` environment variables instead of using the flags.
When you authenticate with universal auth, an access token will be printed to the console upon successful login. This token can be used to authenticate with the Infisical API and the CLI by passing it in the `--token` flag when applicable.
Use flag `--plain` along with `--silent` to print only the token in plain text when using the `universal-auth` method.
</Info>
</Accordion>
<Accordion title="--client-id">
```bash
infisical login --client-id=<client-id> # Optional, required if --method=universal-auth.
```
#### Description
The client ID of the universal auth client. This is required if the `--method` flag is set to `universal-auth`.
<Tip>
The `client-id` flag can be substituted with the `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` environment variable.
</Tip>
</Accordion>
<Accordion title="--client-secret">
```bash
infisical login --client-secret=<client-secret> # Optional, required if --method=universal-auth.
```
#### Description
The client secret of the universal auth client. This is required if the `--method` flag is set to `universal-auth`.
<Tip>
The `client-secret` flag can be substituted with the `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` environment variable.
</Tip>
</Accordion>

View File

@ -0,0 +1,21 @@
---
title: "infisical token"
description: "Manage your Infisical identity access tokens"
---
```bash
infisical service-token renew <ua-access-token>
```
## Description
The Infisical `token` command allows you to manage your universal auth access tokens.
With this command, you can renew your access tokens. In the future more subcommands will be added to better help you manage your tokens through the CLI.
<Accordion title="token renew <access-token>" defaultOpen="true">
Use this command to renew your access token. This command will renew your access token and output a renewed access token to the console.
```bash
$ infisical token renew <ua-access-token>
```
</Accordion>

View File

@ -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`.
3. Enter your current password and click `Save`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@ -0,0 +1,76 @@
---
title: "AWS Amplify"
description: "Learn how to sync secrets from Infisical to AWS Amplify."
---
Prerequisites:
- Infisical Cloud account
- Add the secrets you wish to sync to Amplify to [Infisical Cloud](https://app.infisical.com)
There are many approaches to sync secrets stored within Infisical to AWS Amplify. This guide describes two such approaches below.
## Access Infisical secrets at Amplify build time
This approach enables you to fetch secrets from Infisical during Amplify build time.
<Steps>
<Step title="Generate a service token">
Go to your project settings in the Infisical dashboard to generate a [service token](/documentation/platform/token). This service token will allow you to authenticate and fetch secrets from Infisical. Once you have created a service token with the required permissions, youll need to provide the token to the CLI installed in your Docker container.
</Step>
<Step title="Set the service token as an Amplify environment variable">
![aws amplify env console](../../images/integrations/aws/integrations-amplify-env-console.png)
1. In the Amplify console, choose App Settings, and then select Environment variables.
2. In the Environment variables section, select Manage variables.
3. Under Variable, enter the key **INFISICAL_TOKEN**. For the value, enter the generated service token from the previous step.
4. Click save.
</Step>
<Step title="Install Infisical CLI to the Amplify build step">
In the prebuild phase, add the command in AWS Amplify to install the Infisical CLI.
```yaml
build:
phases:
preBuild:
commands:
- sudo curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' | sudo -E bash
- sudo yum install infisical
```
</Step>
<Step title="Modify the build command">
You can now pull secrets from Infisical using the CLI and save them as a `.env` file. To do this, modify the build commands.
```yaml
build:
phases:
build:
commands:
- INFISICAL_TOKEN=${INFISICAL_TOKEN}
- infisical export --format=dotenv > .env
- <rest of the commands>
```
</Step>
</Steps>
## Sync Secrets Using AWS SSM Parameter Store
Another approach to use secrets from Infisical in AWS Amplify is to utilize AWS Parameter Store.
At high level, you begin by using Infisical's AWS SSM Parameter Store integration to sync secrets from Infisical to AWS SSM Parameter Store. You then instruct AWS Amplify to consume those secrets from AWS SSM Parameter Store as [environment secrets](https://docs.aws.amazon.com/amplify/latest/userguide/environment-variables.html#environment-secrets).
<Steps>
<Step title="Follow the AWS SSM Parameter Store Integration guide">
Follow the [Infisical AWS SSM Parameter Store Integration Guide](./aws-parameter-store) to set up the integration. Pause once you reach the step where it asks you to select the path you would like to sync.
</Step>
<Step title="Find your Amplify App ID">
![amplify app id](../../images/integrations/aws/integrations-amplify-app-id.png)
1. Open your AWS Amplify App console.
2. Go to **Actions >> View App Settings**
3. The App ID will be the last part of the App ARN field after the slash.
</Step>
<Step title="Set AWS SSM Parameter Store path">
You need to set the path in the format `/amplify/[amplify_app_id]/[your-amplify-environment-name]` as the path option in AWS SSM Parameter Infisical Integration.
</Step>
</Steps>
<Info>
Accessing an environment secret during a build is similar to accessing environment variables, except that environment secrets are stored in `process.env.secrets` as a JSON string.
</Info>

View File

@ -92,6 +92,7 @@ spec:
managedSecretReference:
secretName: managed-secret
secretNamespace: default
creationPolicy: "Orphan" ## Owner | Orphan (default)
# secretType: kubernetes.io/dockerconfigjson
```
### InfisicalSecret CRD properties
@ -232,6 +233,19 @@ The namespace of the managed Kubernetes secret to be created.
</Accordion>
<Accordion title="managedSecretReference.secretType">
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
</Accordion>
<Accordion title="managedSecretReference.creationPolicy">
Creation polices allow you to control whether or not owner references should be added to the managed Kubernetes secret that is generated by the Infisical operator.
This is useful for tools such as ArgoCD, where every resource requires an owner reference; otherwise, it will be pruned automatically.
#### Available options
- `Orphan` (default)
- `Owner`
<Tip>
When creation policy is set to `Owner`, the `InfisicalSecret` CRD must be in the same namespace as where the managed kubernetes secret.
</Tip>
</Accordion>
### Propagating labels & annotations

View File

@ -209,6 +209,13 @@
"self-hosting/guides/mongo-to-postgres"
]
},
{
"group": "Reference architectures",
"pages": [
"self-hosting/reference-architectures/aws-ecs",
"self-hosting/reference-architectures/on-premise"
]
},
"self-hosting/ee",
"self-hosting/faq"
]
@ -226,6 +233,7 @@
"cli/commands/run",
"cli/commands/secrets",
"cli/commands/export",
"cli/commands/token",
"cli/commands/service-token",
"cli/commands/vault",
"cli/commands/user",
@ -277,7 +285,8 @@
"group": "AWS",
"pages": [
"integrations/cloud/aws-parameter-store",
"integrations/cloud/aws-secret-manager"
"integrations/cloud/aws-secret-manager",
"integrations/cloud/aws-amplify"
]
},
"integrations/cloud/vercel",

View File

@ -5,7 +5,7 @@ description: "Learn how to run Postgres schema migrations."
Running schema migrations is a requirement before deploying Infisical.
Each time you decide to upgrade your version of Infisical, it's necessary to run schema migrations for that specific version.
The guide below outlines a step-by-step guide to help you through this process.
The guide below outlines a step-by-step guide to help you manually run schema migrations for Infisical.
### Prerequisites
- Docker installed on your machine

View File

@ -1,21 +0,0 @@
---
title: "AWS EC2"
description: "Learn to install Infisical on EC2 using Cloud Formation template"
---
<iframe width="560" height="315" src="https://www.youtube.com/embed/jR-gM7vIY2c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
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
<Info>
Once installation is complete, you will have to create the first account. No default account is provided.
</Info>
<a href="https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/review?templateURL=https://ec2-instance-cloudformation.s3.amazonaws.com/infisical-ec2-deployment.template&stackName=infisical">
<img width="200" src="../../images/deploy-aws-button.png" />
</a>

View File

@ -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/)
<Steps>
<Step title="Create a container service in AWS Lightsail">
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.
<Note>
To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars).
</Note>
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)
</Step>
<Step title="Navigate to your deployed instance of Infisical">
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)
</Step>
</Steps>
<AccordionGroup>
<Accordion title="Do you have any recommendations for deploying Infisical with AWS Lightsail?">
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!
</Accordion>
</AccordionGroup>

View File

@ -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)
<Steps>
<Step title="Create a Web App in Azure App Services">
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).
<Note>
To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars).
</Note>
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)
</Step>
<Step title="Navigate to your deployed instance of Infisical">
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)
</Step>
</Steps>
<AccordionGroup>
<Accordion title="Do you have any recommendations for deploying Infisical with Azure App Services?">
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!
</Accordion>
</AccordionGroup>

View File

@ -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)
<Note>
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).
</Note>
<Steps>
<Step title="Create a container instance in Azure Container Instances">
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)
<Note>
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.
</Note>
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.
<Note>
To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars).
</Note>
![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)
</Step>
<Step title="Navigate to your deployed instance of Infisical">
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)
</Step>
</Steps>
<AccordionGroup>
<Accordion title="Do you have any recommendations for deploying Infisical with Azure Container Instances?">
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!
</Accordion>
</AccordionGroup>

View File

@ -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.
<a href="https://docs.digitalocean.com/products/marketplace/catalog/infisical/">
<img src="../../images/do-k8-install-btn.png" width="300"/>
</a>
### 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.

View File

@ -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/)
<Steps>
<Step title="Create an app with Fly.io">
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
```
</Step>
<Step title="Edit the fly.toml configuration file">
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"
```
<Note>
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.
</Note>
</Step>
<Step title="Set secrets for your Fly.io app">
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).
<Tabs>
<Tab title="CLI">
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...
```
</Tab>
<Tab title="Dashboard">
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)
</Tab>
</Tabs>
<Note>
To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars).
</Note>
</Step>
<Step title="Deploy the Fly.io app">
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
```
</Step>
</Steps>
<AccordionGroup>
<Accordion title="Do you have any recommendations for deploying Infisical with Fly.io?">
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!
</Accordion>
</AccordionGroup>
Resources:
- [Fly.io documentation](https://fly.io/docs/)

View File

@ -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/)
<Steps>
<Step title="Create a project in GCP">
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)
</Step>
<Step title="Create a service in GCP Cloud Run">
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.
<Note>
To use more features like emailing and single sign-on, you can set additional configuration options [here](/self-hosting/configuration/envars).
</Note>
![GCP Cloud Run create service environment variable specification](/images/self-hosting/deployment-options/gcp-cloud-run/gcp-cloud-run-create-service-envars.png)
<Note>
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.
</Note>
Finally, press **Create** to finish setting up the GCP Cloud Run service.
</Step>
<Step title="Navigate to your deployed instance of Infisical">
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)
</Step>
</Steps>
<AccordionGroup>
<Accordion title="Do you have any recommendations for deploying Infisical with GCP Cloud Run?">
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!
</Accordion>
</AccordionGroup>

View File

@ -1,61 +0,0 @@
---
title: "Railway"
description: "Deploy Infisical with Railway"
---
Prerequisites:
- Have an account with [Railway](https://railway.app/)
<Steps>
<Step title="Deploy the Infisical template with Railway">
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)
<Note>
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).
</Note>
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)
</Step>
<Step title="Navigate to your deployed instance of Infisical">
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)
</Step>
</Steps>
<AccordionGroup>
<Accordion title="Do you have any recommendations for deploying Infisical with Railway?">
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!
</Accordion>
</AccordionGroup>

View File

@ -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)**
#
<Info>
Once installation is complete, you will have to create the first account. No default account is provided.
</Info>

View File

@ -0,0 +1,56 @@
---
title: "AWS ECS"
description: "Reference architecture for self-hosting Infisical on AWS ECS"
---
This guide will provide high-level architecture design for deploying the Infisical on AWS ECS and give insights into the core components, high availability strategies, and secure credential management for Infisical's root secrets.
## Overview
In this guide, we'll focus on running Infisical on AWS Elastic Container Service (ECS) across multiple Availability Zones (AZs), ensuring high availability and resilience.
The architecture utilizes Amazon Relational Database Service (RDS) for persistent storage, ElastiCache for Redis as an in-memory data store for caching, and Amazon Simple Email Service (SES) to handle email based communications from Infisical.
![AWS ECS architecture](/images/self-hosting/reference-architectures/Infisical-AWS-ECS-architecture.jpeg)
### Core Components
- **ECS Fargate:** In this architecture, Infisical is deployed on ECS using Fargate launch type. The ECS services are deployed across multiple Availability Zones to ensure high availability.
- **Amazon RDS:** Infisical uses Postgres as it's persistent layer. As such, RDS for PostgreSQL is used as the database engine. The setup includes a primary instance in one AZ and a read replica in another AZ.
This ensures that if there is a failure in one availability zone, the working replica will become the primary and continue processing workloads.
- **Amazon ElastiCache for Redis:** To enhance performance, Infisical requires Redis. In this architecture, Redis is set up with a primary and standby replication group across two AZs to increase availability.
- **Amazon Simple Email Service (SES):** Infisical requires email service to facilitate outbound communication. AWS SES is integrated into the architecture to handle such communication.
### Network Setup
- **Public Subnets:** Each Availability Zone contains a public subnet. There are two main reasons you might need internet access. First, if you intend to use Infisical to communicate with external secrets managers not located within your virtual private network, enabling internet access is necessary. Second, downloading the Docker image from Docker Hub requires internet access, though this can be avoided by utilizing AWS ECR with VPC Endpoints through AWS Private Link.
- **NAT Gateway:** This is used to route outbound requests from Infisical to the internet and is only used to communicate with external secrets manager and or downloading container images.
### Securing Infisical's root credential
- **Parameter Store:** To secure Infisical's root credentials (database connection string, encryption key, etc), we highly recommend that you use AWS Parameter Store and only allow the tasks running Infisical to access them.
- **AWS Secrets Manager:** We strongly advise securing the master credentials for RDS by utilizing the latest AWS RDS integration with AWS Secrets Manager. This integration automatically stores the master database user's credentials in AWS Secrets Manager, thereby reducing the risk of misplacing the root RDS credential.
### High Availability (HA) and Scalability
- **Multi-AZ Deployment:** By spreading resources across multiple Availability Zones, we ensure that if one AZ experiences issues, traffic can be redirected to the remaining healthy AZ without service interruption.
- **Auto Scaling:** AWS Auto Scaling is in place to adjust capacity to maintain steady and predictable performance at the lowest possible cost.
- **Cross-Region Deployment:** For even greater high availability, you may deploy Infisical across multiple regions. This extends the HA capabilities of the architecture and protects against regional service disruptions.
### Frequently asked questions
<Accordion title="Can Infisical run in an air-gapped environment without any internet access?" defaultOpen >
Yes, Infisical can function in an air-gapped environment. To do so, update your ECS task to use the publicly available AWS Elastic Container Registry (ECR) image instead of the default Docker Hub image. Additionally, it's necessary to configure VPC endpoints, which allows your system to access AWS ECR via a private network route instead of the internet, ensuring all connectivity remains within the secure, private network.
</Accordion>
<Accordion title="Since RDS is in a private subnet, how do run the Postgres schema migrations?">
Since the Amazon RDS instance is housed within a private network to enhance security, it is not directly accessible from the internet. This means that in order to run the required [Postgres schema migrations](/self-hosting/configuration/schema-migrations), you need to connect to this instance of RDS. There are many approaches you can take:
- To automate schema migrations, you may setup CI/CD pipeline with access to the same RDS network to run the schema migrations before making deployment to ECS. This ensures that if migrations fail, your Infisical instances continues to run.
- If you would like to run the migrations manually, consider using AWS Systems Manager Session Manager to access the RDS within the VPC on your local machine.
- If your organization already has mechanisms in place for secure access to the VPC, such as VPNs or Direct Connect, these can also be utilized for performing database migrations manually.
</Accordion>

View File

@ -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.

View File

@ -20,7 +20,8 @@ export enum DynamicSecretProviders {
}
export enum SqlProviders {
Postgres = "postgres"
Postgres = "postgres",
MySql = "mysql2"
}
export type TDynamicSecretProvider = {
@ -34,7 +35,7 @@ export type TDynamicSecretProvider = {
password: string;
creationStatement: string;
revocationStatement: string;
renewStatement: string;
renewStatement?: string;
ca?: string | undefined;
};
};

View File

@ -22,7 +22,8 @@ export const LogsSection = () => {
resolver: yupResolver(auditLogFilterFormSchema),
defaultValues: {
page: 1,
perPage: 10
perPage: 10,
startDate: new Date(new Date().setDate(new Date().getDate() - 1))
}
});

View File

@ -31,7 +31,7 @@ const formSchema = z.object({
password: z.string().min(1),
creationStatement: z.string().min(1),
revocationStatement: z.string().min(1),
renewStatement: z.string().min(1),
renewStatement: z.string().optional(),
ca: z.string().optional()
}),
defaultTTL: z.string().superRefine((val, ctx) => {
@ -66,6 +66,26 @@ type Props = {
environment: string;
};
const getSqlStatements = (provider: SqlProviders) => {
if (provider === SqlProviders.MySql) {
return {
creationStatement:
"CREATE USER \"{{username}}\"@'%' IDENTIFIED BY '{{password}}';\nGRANT ALL ON \"{{database}}\".* TO \"{{username}}\"@'%';",
renewStatement: "",
revocationStatement:
'REVOKE ALL PRIVILEGES ON "{{database}}".* FROM "{{username}}"@\'%\';\nDROP USER "{{username}}"@\'%\';'
};
}
return {
creationStatement:
"CREATE USER \"{{username}}\" WITH ENCRYPTED PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';\nGRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"{{username}}\";",
renewStatement: "ALTER ROLE \"{{username}}\" VALID UNTIL '{{expiration}}';",
revocationStatement:
'REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM "{{username}}";\nDROP ROLE "{{username}}";'
};
};
export const SqlDatabaseInputForm = ({
onCompleted,
onCancel,
@ -75,18 +95,13 @@ export const SqlDatabaseInputForm = ({
}: Props) => {
const {
control,
setValue,
formState: { isSubmitting },
handleSubmit
} = useForm<TForm>({
resolver: zodResolver(formSchema),
defaultValues: {
provider: {
creationStatement:
"CREATE USER \"{{username}}\" WITH ENCRYPTED PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';\nGRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"{{username}}\";",
renewStatement: "ALTER ROLE \"{{username}}\" VALID UNTIL '{{expiration}}';",
revocationStatement:
'REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM "{{username}}";\nDROP ROLE "{{username}}";'
}
provider: getSqlStatements(SqlProviders.Postgres)
}
});
@ -182,10 +197,17 @@ export const SqlDatabaseInputForm = ({
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
<Select
value={value}
onValueChange={(val) => onChange(val)}
onValueChange={(val) => {
onChange(val);
const sqlStatment = getSqlStatements(val as SqlProviders);
setValue("provider.creationStatement", sqlStatment.creationStatement);
setValue("provider.renewStatement", sqlStatment.renewStatement);
setValue("provider.revocationStatement", sqlStatment.revocationStatement);
}}
className="w-full border border-mineshaft-500"
>
<SelectItem value={SqlProviders.Postgres}>PostgreSQL</SelectItem>
<SelectItem value={SqlProviders.MySql}>MySQL</SelectItem>
</Select>
</FormControl>
)}

View File

@ -32,7 +32,7 @@ const formSchema = z.object({
password: z.string().min(1),
creationStatement: z.string().min(1),
revocationStatement: z.string().min(1),
renewStatement: z.string().min(1),
renewStatement: z.string().optional(),
ca: z.string().optional()
})
.partial(),
@ -94,7 +94,7 @@ export const EditDynamicSecretSqlProviderForm = ({
}
}
});
const updateDynamicSecret = useUpdateDynamicSecret();
const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => {
@ -186,11 +186,13 @@ export const EditDynamicSecretSqlProviderForm = ({
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
<Select
isDisabled
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
>
<SelectItem value={SqlProviders.Postgres}>PostgreSQL</SelectItem>
<SelectItem value={SqlProviders.MySql}>MySQL</SelectItem>
</Select>
</FormControl>
)}
@ -221,7 +223,11 @@ export const EditDynamicSecretSqlProviderForm = ({
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} type="number" onChange={(el) => field.onChange(parseInt(el.target.value, 10))} />
<Input
{...field}
type="number"
onChange={(el) => field.onChange(parseInt(el.target.value, 10))}
/>
</FormControl>
)}
/>

View File

@ -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)));