Compare commits
199 Commits
feat/add-p
...
daniel/tls
Author | SHA1 | Date | |
---|---|---|---|
6e1dc7375c | |||
164627139e | |||
021dbf3558 | |||
7e9389cb26 | |||
eda57881ec | |||
553d51e5b3 | |||
16e0a441ae | |||
d6c0941fa9 | |||
7cbd254f06 | |||
4b83b92725 | |||
fe72f034c1 | |||
6803553b21 | |||
1c8299054a | |||
98b6373d6a | |||
1d97921c7c | |||
0d4164ea81 | |||
79bd8613d3 | |||
8deea21a83 | |||
3b3c2be933 | |||
c041e44399 | |||
c1aeb04174 | |||
3f3c0aab0f | |||
b740e8c900 | |||
4416b11094 | |||
d8169a866d | |||
7239158e7f | |||
fefe2d1de1 | |||
3f3e41282d | |||
c14f94177a | |||
ceb741955d | |||
f5bc4e1b5f | |||
06900b9c99 | |||
d71cb96adf | |||
61ebec25b3 | |||
57320c51fb | |||
4aa9cd0f72 | |||
ea39ef9269 | |||
15749a1f52 | |||
9e9aff129e | |||
4ac487c974 | |||
2e50072caa | |||
2bd170df7d | |||
938a7b7e72 | |||
af864b456b | |||
a30e3874cd | |||
de886f8dd0 | |||
b3db29ac37 | |||
ce1db38afd | |||
0fa6b7a08a | |||
29c5bf5491 | |||
4d711ae149 | |||
9dd675ff98 | |||
8fd3e50d04 | |||
391ed0723e | |||
84af8e708e | |||
b39b5bd1a1 | |||
b3d9d91b52 | |||
5ad4061881 | |||
f29862eaf2 | |||
7cb174b644 | |||
bf00d16c80 | |||
e30a0fe8be | |||
6e6f0252ae | |||
2348df7a4d | |||
962cf67dfb | |||
32627c20c4 | |||
c50f8fd78c | |||
1cb4dc9e84 | |||
977ce09245 | |||
08d7dead8c | |||
a30e06e392 | |||
23f3f09cb6 | |||
5cd0f665fa | |||
443e76c1df | |||
4ea22b6761 | |||
ae7e0d0963 | |||
ed6c6d54c0 | |||
428ff5186f | |||
d07b0d20d6 | |||
8e373fe9bf | |||
28087cdcc4 | |||
dcef49950d | |||
1e5d567ef7 | |||
d09c320150 | |||
229599b8de | |||
02eea4d886 | |||
d12144a7e7 | |||
5fa69235d1 | |||
7dd9337b1c | |||
f9eaee4dbc | |||
cd3a64f3e7 | |||
121254f98d | |||
1591c1dbac | |||
3c59d288c4 | |||
632b775d7f | |||
d66da3d770 | |||
da43f405c4 | |||
d5c0abbc3b | |||
7a642e7634 | |||
de686acc23 | |||
b359f4278e | |||
29d76c1deb | |||
6ba1012f5b | |||
4abb3ef348 | |||
73e764474d | |||
7eb5689b4c | |||
5d945f432d | |||
1066710c4f | |||
b64d4e57c4 | |||
bd860e6c5a | |||
37137b8c68 | |||
8b10cf863d | |||
eb45bed7d9 | |||
1ee65205a0 | |||
f41272d4df | |||
8bf4df9f27 | |||
037a8f2ebb | |||
14bc436283 | |||
a108c7dde1 | |||
54ccd73d2a | |||
729ca7b6d6 | |||
754db67f11 | |||
f97756a07b | |||
22df51ab8e | |||
bff8f55ea2 | |||
2f17f5e7df | |||
72d2247bf2 | |||
4ecd4c0337 | |||
538613dd40 | |||
4c5c24f689 | |||
dead16a98a | |||
224368b172 | |||
3731459e99 | |||
dc055c11ab | |||
22878a035b | |||
2f2c9d4508 | |||
774017adbe | |||
f9d1d9c89f | |||
eb82fc0d9a | |||
e45585a909 | |||
6f0484f074 | |||
4ba529f22d | |||
5360fb033a | |||
27e14bcafe | |||
bc5003ae4c | |||
f544b39597 | |||
8381f52f1e | |||
aa96a833d7 | |||
53c64b759c | |||
74f2224c6b | |||
ecb5342a55 | |||
bcb657b81e | |||
ebe6b08cab | |||
43b14d0091 | |||
7127f6d1e1 | |||
20387cff35 | |||
997d7f22fc | |||
e1ecad2331 | |||
ce26a06129 | |||
7622cac07e | |||
a101602e0a | |||
ca63a7baa7 | |||
ff4f15c437 | |||
d6c2715852 | |||
fc386c0cbc | |||
263a88379f | |||
4b718b679a | |||
498b1109c9 | |||
b70bf4cadb | |||
d301f74feb | |||
454826fbb6 | |||
f464d7a096 | |||
cae9ace1ca | |||
8a5a295a01 | |||
95a4661787 | |||
7e9c846ba3 | |||
aed310b9ee | |||
c331af5345 | |||
d4dd684f32 | |||
1f6c33bdb8 | |||
a538e37a62 | |||
f3f87cfd84 | |||
2c57bd94fb | |||
869fcd6541 | |||
7b3e116bf8 | |||
0a95f6dc1d | |||
d19c856e9b | |||
ada0033bd0 | |||
6818c8730f | |||
8542ec8c3e | |||
c141b916d3 | |||
b09dddec1c | |||
1ae375188b | |||
22b954b657 | |||
1d6d424c91 | |||
c39ea130b1 | |||
5514508482 | |||
5921dcaa51 | |||
b2c62c4193 |
13
.github/workflows/build-binaries.yml
vendored
@ -14,7 +14,6 @@ defaults:
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x64, arm64]
|
||||
@ -24,6 +23,7 @@ jobs:
|
||||
target: node20-linux
|
||||
- os: win
|
||||
target: node20-win
|
||||
runs-on: ${{ (matrix.arch == 'arm64' && matrix.os == 'linux') && 'ubuntu24-arm64' || 'ubuntu-latest' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@ -49,9 +49,9 @@ jobs:
|
||||
- name: Package into node binary
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" != "linux" ]; then
|
||||
pkg --no-bytecode --public-packages "*" --public --compress Brotli --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
|
||||
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
|
||||
else
|
||||
pkg --no-bytecode --public-packages "*" --public --compress Brotli --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
|
||||
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
|
||||
fi
|
||||
|
||||
# Set up .deb package structure (Debian/Ubuntu only)
|
||||
@ -84,7 +84,12 @@ jobs:
|
||||
mv infisical-core.deb ./binary/infisical-core-${{matrix.arch}}.deb
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
- run: pip install --upgrade cloudsmith-cli
|
||||
with:
|
||||
python-version: "3.x" # Specify the Python version you need
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade cloudsmith-cli
|
||||
|
||||
# Publish .deb file to Cloudsmith (Debian/Ubuntu only)
|
||||
- name: Publish to Cloudsmith (Debian/Ubuntu)
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalPolicy, "enforcementLevel");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.table(TableName.SecretApprovalPolicy, (table) => {
|
||||
table.string("enforcementLevel", 10).notNullable().defaultTo(EnforcementLevel.Hard);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalPolicy, "enforcementLevel");
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(TableName.SecretApprovalPolicy, (table) => {
|
||||
table.dropColumn("enforcementLevel");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "enforcementLevel");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.table(TableName.AccessApprovalPolicy, (table) => {
|
||||
table.string("enforcementLevel", 10).notNullable().defaultTo(EnforcementLevel.Hard);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "enforcementLevel");
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(TableName.AccessApprovalPolicy, (table) => {
|
||||
table.dropColumn("enforcementLevel");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { SecretSharingAccessType } from "@app/lib/types";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretSharing, "accessType");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.table(TableName.SecretSharing, (table) => {
|
||||
table.string("accessType").notNullable().defaultTo(SecretSharingAccessType.Anyone);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretSharing, "accessType");
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(TableName.SecretSharing, (table) => {
|
||||
table.dropColumn("accessType");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalRequest, "bypassReason");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.table(TableName.SecretApprovalRequest, (table) => {
|
||||
table.string("bypassReason").nullable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalRequest, "bypassReason");
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(TableName.SecretApprovalRequest, (table) => {
|
||||
table.dropColumn("bypassReason");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||
const doesNameExist = await knex.schema.hasColumn(TableName.SecretSharing, "name");
|
||||
if (!doesNameExist) {
|
||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||
t.string("name").nullable();
|
||||
});
|
||||
}
|
||||
|
||||
const doesLastViewedAtExist = await knex.schema.hasColumn(TableName.SecretSharing, "lastViewedAt");
|
||||
if (!doesLastViewedAtExist) {
|
||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||
t.timestamp("lastViewedAt").nullable();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||
const doesNameExist = await knex.schema.hasColumn(TableName.SecretSharing, "name");
|
||||
if (doesNameExist) {
|
||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||
t.dropColumn("name");
|
||||
});
|
||||
}
|
||||
|
||||
const doesLastViewedAtExist = await knex.schema.hasColumn(TableName.SecretSharing, "lastViewedAt");
|
||||
if (doesLastViewedAtExist) {
|
||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||
t.dropColumn("lastViewedAt");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,8 @@ export const AccessApprovalPoliciesSchema = z.object({
|
||||
secretPath: z.string().nullable().optional(),
|
||||
envId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
enforcementLevel: z.string().default("hard")
|
||||
});
|
||||
|
||||
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
||||
|
@ -13,9 +13,9 @@ export const KmsKeysSchema = z.object({
|
||||
isDisabled: z.boolean().default(false).nullable().optional(),
|
||||
isReserved: z.boolean().default(true).nullable().optional(),
|
||||
orgId: z.string().uuid(),
|
||||
slug: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
slug: z.string()
|
||||
updatedAt: z.date()
|
||||
});
|
||||
|
||||
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
||||
|
@ -18,7 +18,7 @@ export const OrgMembershipsSchema = z.object({
|
||||
orgId: z.string().uuid(),
|
||||
roleId: z.string().uuid().nullable().optional(),
|
||||
projectFavorites: z.string().array().nullable().optional(),
|
||||
isActive: z.boolean()
|
||||
isActive: z.boolean().default(true)
|
||||
});
|
||||
|
||||
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;
|
||||
|
@ -14,7 +14,8 @@ export const SecretApprovalPoliciesSchema = z.object({
|
||||
approvals: z.number().default(1),
|
||||
envId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
enforcementLevel: z.string().default("hard")
|
||||
});
|
||||
|
||||
export type TSecretApprovalPolicies = z.infer<typeof SecretApprovalPoliciesSchema>;
|
||||
|
@ -19,7 +19,8 @@ export const SecretApprovalRequestsSchema = z.object({
|
||||
updatedAt: z.date(),
|
||||
isReplicated: z.boolean().nullable().optional(),
|
||||
committerUserId: z.string().uuid(),
|
||||
statusChangedByUserId: z.string().uuid().nullable().optional()
|
||||
statusChangedByUserId: z.string().uuid().nullable().optional(),
|
||||
bypassReason: z.string().nullable().optional()
|
||||
});
|
||||
|
||||
export type TSecretApprovalRequests = z.infer<typeof SecretApprovalRequestsSchema>;
|
||||
|
@ -18,7 +18,10 @@ export const SecretSharingSchema = z.object({
|
||||
orgId: z.string().uuid().nullable().optional(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
expiresAfterViews: z.number().nullable().optional()
|
||||
expiresAfterViews: z.number().nullable().optional(),
|
||||
accessType: z.string().default("anyone"),
|
||||
name: z.string().nullable().optional(),
|
||||
lastViewedAt: z.date().nullable().optional()
|
||||
});
|
||||
|
||||
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -17,7 +18,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
secretPath: z.string().trim().default("/"),
|
||||
environment: z.string(),
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1)
|
||||
approvals: z.number().min(1).default(1),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||
})
|
||||
.refine((data) => data.approvals <= data.approvers.length, {
|
||||
path: ["approvals"],
|
||||
@ -38,7 +40,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body,
|
||||
projectSlug: req.body.projectSlug,
|
||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`
|
||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
|
||||
enforcementLevel: req.body.enforcementLevel
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
@ -115,7 +118,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
.optional()
|
||||
.transform((val) => (val === "" ? "/" : val)),
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1)
|
||||
approvals: z.number().min(1).default(1),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||
})
|
||||
.refine((data) => data.approvals <= data.approvers.length, {
|
||||
path: ["approvals"],
|
||||
|
@ -99,7 +99,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
||||
approvals: z.number(),
|
||||
approvers: z.string().array(),
|
||||
secretPath: z.string().nullish(),
|
||||
envId: z.string()
|
||||
envId: z.string(),
|
||||
enforcementLevel: z.string()
|
||||
}),
|
||||
reviewers: z
|
||||
.object({
|
||||
|
@ -52,6 +52,36 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:organizationId/roles/:roleId",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
roleId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
role: OrgRolesSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const role = await server.services.orgRole.getRole(
|
||||
req.permission.id,
|
||||
req.params.organizationId,
|
||||
req.params.roleId,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { role };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/:organizationId/roles/:roleId",
|
||||
@ -69,7 +99,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
.trim()
|
||||
.optional()
|
||||
.refine(
|
||||
(val) => typeof val === "undefined" || Object.keys(OrgMembershipRole).includes(val),
|
||||
(val) => typeof val !== "undefined" && !Object.keys(OrgMembershipRole).includes(val),
|
||||
"Please choose a different slug, the slug you have entered is reserved."
|
||||
)
|
||||
.refine((val) => typeof val === "undefined" || slugify(val) === val, {
|
||||
@ -77,7 +107,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
}),
|
||||
name: z.string().trim().optional(),
|
||||
description: z.string().trim().optional(),
|
||||
permissions: z.any().array()
|
||||
permissions: z.any().array().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -101,7 +101,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
message: "Slug must be a valid"
|
||||
}),
|
||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions)
|
||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -120,7 +120,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
roleId: req.params.roleId,
|
||||
data: {
|
||||
...req.body,
|
||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
||||
permissions: req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined
|
||||
}
|
||||
});
|
||||
return { role };
|
||||
|
@ -350,7 +350,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
schemas: z.array(z.string()),
|
||||
id: z.string().trim(),
|
||||
displayName: z.string().trim(),
|
||||
members: z.array(z.any()).length(0),
|
||||
members: z.array(
|
||||
z.object({
|
||||
value: z.string(),
|
||||
display: z.string()
|
||||
})
|
||||
),
|
||||
meta: z.object({
|
||||
resourceType: z.string().trim()
|
||||
})
|
||||
@ -423,7 +428,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
displayName: z.string().trim(),
|
||||
members: z.array(
|
||||
z.object({
|
||||
value: z.string(), // infisical orgMembershipId
|
||||
value: z.string(),
|
||||
display: z.string()
|
||||
})
|
||||
)
|
||||
|
@ -2,6 +2,7 @@ import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
@ -24,11 +25,13 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
.string()
|
||||
.optional()
|
||||
.nullable()
|
||||
.default("/")
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||
approverUserIds: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1)
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||
})
|
||||
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||
.refine((data) => data.approvals <= data.approvers.length, {
|
||||
path: ["approvals"],
|
||||
message: "The number of approvals should be lower than the number of approvers."
|
||||
}),
|
||||
@ -47,7 +50,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.body.workspaceId,
|
||||
...req.body,
|
||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`
|
||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
|
||||
enforcementLevel: req.body.enforcementLevel
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
@ -66,15 +70,17 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
body: z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
approverUserIds: z.string().array().min(1),
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
secretPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.nullable()
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||
.transform((val) => (val === "" ? "/" : val)),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).optional()
|
||||
})
|
||||
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||
.refine((data) => data.approvals <= data.approvers.length, {
|
||||
path: ["approvals"],
|
||||
message: "The number of approvals should be lower than the number of approvers."
|
||||
}),
|
||||
|
@ -49,7 +49,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
name: z.string(),
|
||||
approvals: z.number(),
|
||||
approvers: z.string().array(),
|
||||
secretPath: z.string().optional().nullable()
|
||||
secretPath: z.string().optional().nullable(),
|
||||
enforcementLevel: z.string()
|
||||
}),
|
||||
committerUser: approvalRequestUser,
|
||||
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
||||
@ -116,6 +117,9 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
params: z.object({
|
||||
id: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
bypassReason: z.string().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
approval: SecretApprovalRequestsSchema
|
||||
@ -129,7 +133,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
approvalId: req.params.id
|
||||
approvalId: req.params.id,
|
||||
bypassReason: req.body.bypassReason
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
@ -248,7 +253,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
name: z.string(),
|
||||
approvals: z.number(),
|
||||
approvers: approvalRequestUser.array(),
|
||||
secretPath: z.string().optional().nullable()
|
||||
secretPath: z.string().optional().nullable(),
|
||||
enforcementLevel: z.string()
|
||||
}),
|
||||
environment: z.string(),
|
||||
statusChangedByUser: approvalRequestUser.optional(),
|
||||
|
@ -47,7 +47,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
approvals,
|
||||
approvers,
|
||||
projectSlug,
|
||||
environment
|
||||
environment,
|
||||
enforcementLevel
|
||||
}: TCreateAccessApprovalPolicy) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
@ -94,7 +95,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
envId: env.id,
|
||||
approvals,
|
||||
secretPath,
|
||||
name
|
||||
name,
|
||||
enforcementLevel
|
||||
},
|
||||
tx
|
||||
);
|
||||
@ -143,7 +145,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals
|
||||
approvals,
|
||||
enforcementLevel
|
||||
}: TUpdateAccessApprovalPolicy) => {
|
||||
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
||||
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
||||
@ -163,7 +166,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
{
|
||||
approvals,
|
||||
secretPath,
|
||||
name
|
||||
name,
|
||||
enforcementLevel
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
|
||||
import { ActorAuthMethod } from "@app/services/auth/auth-type";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
@ -20,6 +20,7 @@ export type TCreateAccessApprovalPolicy = {
|
||||
approvers: string[];
|
||||
projectSlug: string;
|
||||
name: string;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateAccessApprovalPolicy = {
|
||||
@ -28,6 +29,7 @@ export type TUpdateAccessApprovalPolicy = {
|
||||
approvers?: string[];
|
||||
secretPath?: string;
|
||||
name?: string;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDeleteAccessApprovalPolicy = {
|
||||
|
@ -48,6 +48,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
db.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
|
||||
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
|
||||
)
|
||||
|
||||
@ -98,6 +99,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
name: doc.policyName,
|
||||
approvals: doc.policyApprovals,
|
||||
secretPath: doc.policySecretPath,
|
||||
enforcementLevel: doc.policyEnforcementLevel,
|
||||
envId: doc.policyEnvId
|
||||
},
|
||||
privilege: doc.privilegeId
|
||||
@ -165,6 +167,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
tx.ref("projectId").withSchema(TableName.Environment),
|
||||
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||
tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)
|
||||
);
|
||||
@ -184,7 +187,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
id: el.policyId,
|
||||
name: el.policyName,
|
||||
approvals: el.policyApprovals,
|
||||
secretPath: el.policySecretPath
|
||||
secretPath: el.policySecretPath,
|
||||
enforcementLevel: el.policyEnforcementLevel
|
||||
}
|
||||
}),
|
||||
childrenMapper: [
|
||||
|
@ -106,6 +106,7 @@ export enum EventType {
|
||||
CREATE_ENVIRONMENT = "create-environment",
|
||||
UPDATE_ENVIRONMENT = "update-environment",
|
||||
DELETE_ENVIRONMENT = "delete-environment",
|
||||
GET_ENVIRONMENT = "get-environment",
|
||||
ADD_WORKSPACE_MEMBER = "add-workspace-member",
|
||||
ADD_BATCH_WORKSPACE_MEMBER = "add-workspace-members",
|
||||
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
|
||||
@ -831,6 +832,13 @@ interface CreateEnvironmentEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetEnvironmentEvent {
|
||||
type: EventType.GET_ENVIRONMENT;
|
||||
metadata: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateEnvironmentEvent {
|
||||
type: EventType.UPDATE_ENVIRONMENT;
|
||||
metadata: {
|
||||
@ -1230,6 +1238,7 @@ export type Event =
|
||||
| UpdateIdentityOidcAuthEvent
|
||||
| GetIdentityOidcAuthEvent
|
||||
| CreateEnvironmentEvent
|
||||
| GetEnvironmentEvent
|
||||
| UpdateEnvironmentEvent
|
||||
| DeleteEnvironmentEvent
|
||||
| AddWorkspaceMemberEvent
|
||||
|
@ -336,31 +336,36 @@ export const removeUsersFromGroupByUserIds = async ({
|
||||
)
|
||||
);
|
||||
|
||||
// TODO: this part can be optimized
|
||||
for await (const userId of userIds) {
|
||||
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(userId, group.id, projectIds, tx);
|
||||
const projectsToDeleteKeyFor = projectIds.filter((p) => !t.has(p));
|
||||
const promises: Array<Promise<void>> = [];
|
||||
for (const userId of userIds) {
|
||||
promises.push(
|
||||
(async () => {
|
||||
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(userId, group.id, projectIds, tx);
|
||||
const projectsToDeleteKeyFor = projectIds.filter((p) => !t.has(p));
|
||||
|
||||
if (projectsToDeleteKeyFor.length) {
|
||||
await projectKeyDAL.delete(
|
||||
{
|
||||
receiverId: userId,
|
||||
$in: {
|
||||
projectId: projectsToDeleteKeyFor
|
||||
}
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
if (projectsToDeleteKeyFor.length) {
|
||||
await projectKeyDAL.delete(
|
||||
{
|
||||
receiverId: userId,
|
||||
$in: {
|
||||
projectId: projectsToDeleteKeyFor
|
||||
}
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
await userGroupMembershipDAL.delete(
|
||||
{
|
||||
groupId: group.id,
|
||||
userId
|
||||
},
|
||||
tx
|
||||
await userGroupMembershipDAL.delete(
|
||||
{
|
||||
groupId: group.id,
|
||||
userId
|
||||
},
|
||||
tx
|
||||
);
|
||||
})()
|
||||
);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
if (membersToRemoveFromGroupPending.length) {
|
||||
|
@ -162,17 +162,50 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findUserGroupMembershipsInOrg = async (userId: string, orgId: string) => {
|
||||
const findGroupMembershipsByUserIdInOrg = async (userId: string, orgId: string) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.UserGroupMembership)
|
||||
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
|
||||
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
|
||||
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||
.where(`${TableName.UserGroupMembership}.userId`, userId)
|
||||
.where(`${TableName.Groups}.orgId`, orgId);
|
||||
.where(`${TableName.Groups}.orgId`, orgId)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("groupId").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("name").withSchema(TableName.Groups).as("groupName"),
|
||||
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
|
||||
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
|
||||
db.ref("lastName").withSchema(TableName.Users).as("lastName")
|
||||
);
|
||||
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "findTest" });
|
||||
throw new DatabaseError({ error, name: "Find group memberships by user id in org" });
|
||||
}
|
||||
};
|
||||
|
||||
const findGroupMembershipsByGroupIdInOrg = async (groupId: string, orgId: string) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.UserGroupMembership)
|
||||
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
|
||||
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
|
||||
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||
.where(`${TableName.Groups}.id`, groupId)
|
||||
.where(`${TableName.Groups}.orgId`, orgId)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("groupId").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("name").withSchema(TableName.Groups).as("groupName"),
|
||||
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
|
||||
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
|
||||
db.ref("lastName").withSchema(TableName.Users).as("lastName")
|
||||
);
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find group memberships by group id in org" });
|
||||
}
|
||||
};
|
||||
|
||||
@ -182,6 +215,7 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
||||
findUserGroupMembershipsInProject,
|
||||
findGroupMembersNotInProject,
|
||||
deletePendingUserGroupMembershipsByUserIds,
|
||||
findUserGroupMembershipsInOrg
|
||||
findGroupMembershipsByUserIdInOrg,
|
||||
findGroupMembershipsByGroupIdInOrg
|
||||
};
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-grou
|
||||
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { TOrgPermission } from "@app/lib/types";
|
||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
||||
@ -51,6 +52,7 @@ import {
|
||||
TListScimUsers,
|
||||
TListScimUsersDTO,
|
||||
TReplaceScimUserDTO,
|
||||
TScimGroup,
|
||||
TScimTokenJwtPayload,
|
||||
TUpdateScimGroupNamePatchDTO,
|
||||
TUpdateScimGroupNamePutDTO,
|
||||
@ -83,7 +85,8 @@ type TScimServiceFactoryDep = {
|
||||
| "insertMany"
|
||||
| "filterProjectsByUserMembership"
|
||||
| "delete"
|
||||
| "findUserGroupMembershipsInOrg"
|
||||
| "findGroupMembershipsByUserIdInOrg"
|
||||
| "findGroupMembershipsByGroupIdInOrg"
|
||||
>;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
@ -252,7 +255,10 @@ export const scimServiceFactory = ({
|
||||
status: 403
|
||||
});
|
||||
|
||||
const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
|
||||
const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(
|
||||
membership.userId,
|
||||
orgId
|
||||
);
|
||||
|
||||
return buildScimUser({
|
||||
orgMembershipId: membership.id,
|
||||
@ -263,7 +269,7 @@ export const scimServiceFactory = ({
|
||||
active: membership.isActive,
|
||||
groups: groupMembershipsInOrg.map((group) => ({
|
||||
value: group.groupId,
|
||||
display: group.name
|
||||
display: group.groupName
|
||||
}))
|
||||
});
|
||||
};
|
||||
@ -509,7 +515,10 @@ export const scimServiceFactory = ({
|
||||
isActive: active
|
||||
});
|
||||
|
||||
const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
|
||||
const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(
|
||||
membership.userId,
|
||||
orgId
|
||||
);
|
||||
|
||||
return buildScimUser({
|
||||
orgMembershipId: membership.id,
|
||||
@ -520,7 +529,7 @@ export const scimServiceFactory = ({
|
||||
active,
|
||||
groups: groupMembershipsInOrg.map((group) => ({
|
||||
value: group.groupId,
|
||||
display: group.name
|
||||
display: group.groupName
|
||||
}))
|
||||
});
|
||||
};
|
||||
@ -589,13 +598,20 @@ export const scimServiceFactory = ({
|
||||
}
|
||||
);
|
||||
|
||||
const scimGroups = groups.map((group) =>
|
||||
buildScimGroup({
|
||||
const scimGroups: TScimGroup[] = [];
|
||||
|
||||
for await (const group of groups) {
|
||||
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
|
||||
const scimGroup = buildScimGroup({
|
||||
groupId: group.id,
|
||||
name: group.name,
|
||||
members: [] // does this need to be populated?
|
||||
})
|
||||
);
|
||||
members: members.map((member) => ({
|
||||
value: member.orgMembershipId,
|
||||
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
|
||||
}))
|
||||
});
|
||||
scimGroups.push(scimGroup);
|
||||
}
|
||||
|
||||
return buildScimGroupList({
|
||||
scimGroups,
|
||||
@ -872,23 +888,27 @@ export const scimServiceFactory = ({
|
||||
break;
|
||||
}
|
||||
case "add": {
|
||||
const orgMemberships = await orgMembershipDAL.find({
|
||||
$in: {
|
||||
id: operation.value.map((member) => member.value)
|
||||
}
|
||||
});
|
||||
try {
|
||||
const orgMemberships = await orgMembershipDAL.find({
|
||||
$in: {
|
||||
id: operation.value.map((member) => member.value)
|
||||
}
|
||||
});
|
||||
|
||||
await addUsersToGroupByUserIds({
|
||||
group,
|
||||
userIds: orgMemberships.map((membership) => membership.userId as string),
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL
|
||||
});
|
||||
await addUsersToGroupByUserIds({
|
||||
group,
|
||||
userIds: orgMemberships.map((membership) => membership.userId as string),
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL
|
||||
});
|
||||
} catch {
|
||||
logger.info("Repeat SCIM user-group add operation");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@ -916,10 +936,15 @@ export const scimServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
|
||||
|
||||
return buildScimGroup({
|
||||
groupId: group.id,
|
||||
name: group.name,
|
||||
members: []
|
||||
members: members.map((member) => ({
|
||||
value: member.orgMembershipId,
|
||||
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
|
||||
}))
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -45,12 +45,13 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
approverUserIds,
|
||||
approvers,
|
||||
projectId,
|
||||
secretPath,
|
||||
environment
|
||||
environment,
|
||||
enforcementLevel
|
||||
}: TCreateSapDTO) => {
|
||||
if (approvals > approverUserIds.length)
|
||||
if (approvals > approvers.length)
|
||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
@ -73,12 +74,13 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
envId: env.id,
|
||||
approvals,
|
||||
secretPath,
|
||||
name
|
||||
name,
|
||||
enforcementLevel
|
||||
},
|
||||
tx
|
||||
);
|
||||
await secretApprovalPolicyApproverDAL.insertMany(
|
||||
approverUserIds.map((approverUserId) => ({
|
||||
approvers.map((approverUserId) => ({
|
||||
approverUserId,
|
||||
policyId: doc.id
|
||||
})),
|
||||
@ -90,7 +92,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
};
|
||||
|
||||
const updateSecretApprovalPolicy = async ({
|
||||
approverUserIds,
|
||||
approvers,
|
||||
secretPath,
|
||||
name,
|
||||
actorId,
|
||||
@ -98,7 +100,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
secretPolicyId
|
||||
secretPolicyId,
|
||||
enforcementLevel
|
||||
}: TUpdateSapDTO) => {
|
||||
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
||||
if (!secretApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
||||
@ -118,14 +121,15 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
{
|
||||
approvals,
|
||||
secretPath,
|
||||
name
|
||||
name,
|
||||
enforcementLevel
|
||||
},
|
||||
tx
|
||||
);
|
||||
if (approverUserIds) {
|
||||
if (approvers) {
|
||||
await secretApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
||||
await secretApprovalPolicyApproverDAL.insertMany(
|
||||
approverUserIds.map((approverUserId) => ({
|
||||
approvers.map((approverUserId) => ({
|
||||
approverUserId,
|
||||
policyId: doc.id
|
||||
})),
|
||||
|
@ -1,20 +1,22 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
|
||||
|
||||
export type TCreateSapDTO = {
|
||||
approvals: number;
|
||||
secretPath?: string | null;
|
||||
environment: string;
|
||||
approverUserIds: string[];
|
||||
approvers: string[];
|
||||
projectId: string;
|
||||
name: string;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateSapDTO = {
|
||||
secretPolicyId: string;
|
||||
approvals?: number;
|
||||
secretPath?: string | null;
|
||||
approverUserIds: string[];
|
||||
approvers: string[];
|
||||
name?: string;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDeleteSapDTO = {
|
||||
|
@ -94,6 +94,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
tx.ref("projectId").withSchema(TableName.Environment),
|
||||
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||
tx.ref("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"),
|
||||
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals")
|
||||
);
|
||||
|
||||
@ -128,7 +130,9 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
id: el.policyId,
|
||||
name: el.policyName,
|
||||
approvals: el.policyApprovals,
|
||||
secretPath: el.policySecretPath
|
||||
secretPath: el.policySecretPath,
|
||||
enforcementLevel: el.policyEnforcementLevel,
|
||||
envId: el.policyEnvId
|
||||
}
|
||||
}),
|
||||
childrenMapper: [
|
||||
@ -282,6 +286,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank`
|
||||
),
|
||||
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
||||
@ -308,7 +313,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
id: el.policyId,
|
||||
name: el.policyName,
|
||||
approvals: el.policyApprovals,
|
||||
secretPath: el.policySecretPath
|
||||
secretPath: el.policySecretPath,
|
||||
enforcementLevel: el.policyEnforcementLevel
|
||||
},
|
||||
committerUser: {
|
||||
userId: el.committerUserId,
|
||||
|
@ -7,13 +7,16 @@ import {
|
||||
SecretType,
|
||||
TSecretApprovalRequestsSecretsInsert
|
||||
} from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { groupBy, pick, unique } from "@app/lib/fn";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||
import {
|
||||
fnSecretBlindIndexCheck,
|
||||
@ -30,6 +33,8 @@ import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version
|
||||
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
@ -62,8 +67,11 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
||||
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">;
|
||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findProjectById">;
|
||||
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
userDAL: Pick<TUserDALFactory, "find" | "findOne">;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||
};
|
||||
|
||||
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
||||
@ -82,7 +90,10 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
snapshotService,
|
||||
secretVersionDAL,
|
||||
secretQueueService,
|
||||
projectBotService
|
||||
projectBotService,
|
||||
smtpService,
|
||||
userDAL,
|
||||
projectEnvDAL
|
||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||
@ -257,7 +268,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
actorAuthMethod,
|
||||
bypassReason
|
||||
}: TMergeSecretApprovalRequestDTO) => {
|
||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||
@ -289,7 +301,10 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
({ userId: approverId }) => reviewers[approverId.toString()] === ApprovalStatus.APPROVED
|
||||
).length;
|
||||
|
||||
if (!hasMinApproval) throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
|
||||
|
||||
if (!hasMinApproval && !isSoftEnforcement)
|
||||
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
||||
if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" });
|
||||
|
||||
@ -466,7 +481,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
conflicts: JSON.stringify(conflicts),
|
||||
hasMerged: true,
|
||||
status: RequestState.Closed,
|
||||
statusChangedByUserId: actorId
|
||||
statusChangedByUserId: actorId,
|
||||
bypassReason
|
||||
},
|
||||
tx
|
||||
);
|
||||
@ -485,6 +501,35 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
actor
|
||||
});
|
||||
|
||||
if (isSoftEnforcement) {
|
||||
const cfg = getConfig();
|
||||
const project = await projectDAL.findProjectById(projectId);
|
||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||
const requestedByUser = await userDAL.findOne({ id: actorId });
|
||||
const approverUsers = await userDAL.find({
|
||||
$in: {
|
||||
id: policy.approvers.map((approver: { userId: string }) => approver.userId)
|
||||
}
|
||||
});
|
||||
|
||||
await smtpService.sendMail({
|
||||
recipients: approverUsers.filter((approver) => approver.email).map((approver) => approver.email!),
|
||||
subjectLine: "Infisical Secret Change Policy Bypassed",
|
||||
|
||||
substitutions: {
|
||||
projectName: project.name,
|
||||
requesterFullName: `${requestedByUser.firstName} ${requestedByUser.lastName}`,
|
||||
requesterEmail: requestedByUser.email,
|
||||
bypassReason,
|
||||
secretPath: policy.secretPath,
|
||||
environment: env.name,
|
||||
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval`
|
||||
},
|
||||
template: SmtpTemplates.AccessSecretRequestBypassed
|
||||
});
|
||||
}
|
||||
|
||||
return mergeStatus;
|
||||
};
|
||||
|
||||
|
@ -39,6 +39,7 @@ export type TGenerateSecretApprovalRequestDTO = {
|
||||
|
||||
export type TMergeSecretApprovalRequestDTO = {
|
||||
approvalId: string;
|
||||
bypassReason?: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TStatusChangeDTO = {
|
||||
|
@ -348,10 +348,15 @@ export const ORGANIZATIONS = {
|
||||
LIST_USER_MEMBERSHIPS: {
|
||||
organizationId: "The ID of the organization to get memberships from."
|
||||
},
|
||||
GET_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to get the membership for.",
|
||||
membershipId: "The ID of the membership to get."
|
||||
},
|
||||
UPDATE_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to update the membership for.",
|
||||
membershipId: "The ID of the membership to update.",
|
||||
role: "The new role of the membership."
|
||||
role: "The new role of the membership.",
|
||||
isActive: "The active status of the membership"
|
||||
},
|
||||
DELETE_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to delete the membership from.",
|
||||
@ -420,6 +425,21 @@ export const PROJECTS = {
|
||||
},
|
||||
LIST_INTEGRATION_AUTHORIZATION: {
|
||||
workspaceId: "The ID of the project to list integration auths for."
|
||||
},
|
||||
LIST_CAS: {
|
||||
slug: "The slug of the project to list CAs for.",
|
||||
status: "The status of the CA to filter by.",
|
||||
friendlyName: "The friendly name of the CA to filter by.",
|
||||
commonName: "The common name of the CA to filter by.",
|
||||
offset: "The offset to start from. If you enter 10, it will start from the 10th CA.",
|
||||
limit: "The number of CAs to return."
|
||||
},
|
||||
LIST_CERTIFICATES: {
|
||||
slug: "The slug of the project to list certificates for.",
|
||||
friendlyName: "The friendly name of the certificate to filter by.",
|
||||
commonName: "The common name of the certificate to filter by.",
|
||||
offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.",
|
||||
limit: "The number of certificates to return."
|
||||
}
|
||||
} as const;
|
||||
|
||||
@ -505,6 +525,10 @@ export const ENVIRONMENTS = {
|
||||
DELETE: {
|
||||
workspaceId: "The ID of the project to delete the environment from.",
|
||||
id: "The ID of the environment to delete."
|
||||
},
|
||||
GET: {
|
||||
workspaceId: "The ID of the project the environment belongs to.",
|
||||
id: "The ID of the environment to fetch."
|
||||
}
|
||||
} as const;
|
||||
|
||||
|
@ -42,3 +42,13 @@ export type RequiredKeys<T> = {
|
||||
}[keyof T];
|
||||
|
||||
export type PickRequired<T> = Pick<T, RequiredKeys<T>>;
|
||||
|
||||
export enum EnforcementLevel {
|
||||
Hard = "hard",
|
||||
Soft = "soft"
|
||||
}
|
||||
|
||||
export enum SecretSharingAccessType {
|
||||
Anyone = "anyone",
|
||||
Organization = "organization"
|
||||
}
|
||||
|
@ -457,6 +457,7 @@ export const registerRoutes = async (
|
||||
tokenService,
|
||||
projectDAL,
|
||||
projectMembershipDAL,
|
||||
orgMembershipDAL,
|
||||
projectKeyDAL,
|
||||
smtpService,
|
||||
userDAL,
|
||||
@ -736,7 +737,8 @@ export const registerRoutes = async (
|
||||
|
||||
const secretSharingService = secretSharingServiceFactory({
|
||||
permissionService,
|
||||
secretSharingDAL
|
||||
secretSharingDAL,
|
||||
orgDAL
|
||||
});
|
||||
|
||||
const secretApprovalRequestService = secretApprovalRequestServiceFactory({
|
||||
@ -753,7 +755,10 @@ export const registerRoutes = async (
|
||||
secretApprovalRequestDAL,
|
||||
snapshotService,
|
||||
secretVersionTagDAL,
|
||||
secretQueueService
|
||||
secretQueueService,
|
||||
smtpService,
|
||||
userDAL,
|
||||
projectEnvDAL
|
||||
});
|
||||
|
||||
const accessApprovalPolicyService = accessApprovalPolicyServiceFactory({
|
||||
|
@ -9,6 +9,55 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:workspaceId/environments/:envId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Get Environment",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim().describe(ENVIRONMENTS.GET.workspaceId),
|
||||
envId: z.string().trim().describe(ENVIRONMENTS.GET.id)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
environment: ProjectEnvironmentsSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const environment = await server.services.projectEnv.getEnvironmentById({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectId: req.params.workspaceId,
|
||||
id: req.params.envId
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: environment.projectId,
|
||||
event: {
|
||||
type: EventType.GET_ENVIRONMENT,
|
||||
metadata: {
|
||||
id: environment.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { environment };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:workspaceId/environments",
|
||||
|
@ -78,6 +78,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
lastName: true,
|
||||
id: true
|
||||
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
|
||||
project: ProjectsSchema.pick({ name: true, id: true }),
|
||||
roles: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretSharingSchema } from "@app/db/schemas";
|
||||
import { SecretSharingAccessType } from "@app/lib/types";
|
||||
import {
|
||||
publicEndpointLimit,
|
||||
publicSecretShareCreationLimit,
|
||||
@ -18,21 +19,31 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
offset: z.coerce.number().min(0).max(100).default(0),
|
||||
limit: z.coerce.number().min(1).max(100).default(25)
|
||||
}),
|
||||
response: {
|
||||
200: z.array(SecretSharingSchema)
|
||||
200: z.object({
|
||||
secrets: z.array(SecretSharingSchema),
|
||||
totalCount: z.number()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const sharedSecrets = await req.server.services.secretSharing.getSharedSecrets({
|
||||
const { secrets, totalCount } = await req.server.services.secretSharing.getSharedSecrets({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
orgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.query
|
||||
});
|
||||
|
||||
return sharedSecrets;
|
||||
return {
|
||||
secrets,
|
||||
totalCount
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@ -47,7 +58,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
id: z.string().uuid()
|
||||
}),
|
||||
querystring: z.object({
|
||||
hashedHex: z.string()
|
||||
hashedHex: z.string().min(1)
|
||||
}),
|
||||
response: {
|
||||
200: SecretSharingSchema.pick({
|
||||
@ -55,22 +66,28 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
iv: true,
|
||||
tag: true,
|
||||
expiresAt: true,
|
||||
expiresAfterViews: true
|
||||
expiresAfterViews: true,
|
||||
accessType: true
|
||||
}).extend({
|
||||
orgName: z.string().optional()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const sharedSecret = await req.server.services.secretSharing.getActiveSharedSecretByIdAndHashedHex(
|
||||
req.params.id,
|
||||
req.query.hashedHex
|
||||
);
|
||||
const sharedSecret = await req.server.services.secretSharing.getActiveSharedSecretById({
|
||||
sharedSecretId: req.params.id,
|
||||
hashedHex: req.query.hashedHex,
|
||||
orgId: req.permission?.orgId
|
||||
});
|
||||
if (!sharedSecret) return undefined;
|
||||
return {
|
||||
encryptedValue: sharedSecret.encryptedValue,
|
||||
iv: sharedSecret.iv,
|
||||
tag: sharedSecret.tag,
|
||||
expiresAt: sharedSecret.expiresAt,
|
||||
expiresAfterViews: sharedSecret.expiresAfterViews
|
||||
expiresAfterViews: sharedSecret.expiresAfterViews,
|
||||
accessType: sharedSecret.accessType,
|
||||
orgName: sharedSecret.orgName
|
||||
};
|
||||
}
|
||||
});
|
||||
@ -84,11 +101,11 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
body: z.object({
|
||||
encryptedValue: z.string(),
|
||||
hashedHex: z.string(),
|
||||
iv: z.string(),
|
||||
tag: z.string(),
|
||||
hashedHex: z.string(),
|
||||
expiresAt: z.string(),
|
||||
expiresAfterViews: z.number()
|
||||
expiresAfterViews: z.number().min(1).optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -97,14 +114,9 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { encryptedValue, iv, tag, hashedHex, expiresAt, expiresAfterViews } = req.body;
|
||||
const sharedSecret = await req.server.services.secretSharing.createPublicSharedSecret({
|
||||
encryptedValue,
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt: new Date(expiresAt),
|
||||
expiresAfterViews
|
||||
...req.body,
|
||||
accessType: SecretSharingAccessType.Anyone
|
||||
});
|
||||
return { id: sharedSecret.id };
|
||||
}
|
||||
@ -118,12 +130,14 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
},
|
||||
schema: {
|
||||
body: z.object({
|
||||
name: z.string().max(50).optional(),
|
||||
encryptedValue: z.string(),
|
||||
hashedHex: z.string(),
|
||||
iv: z.string(),
|
||||
tag: z.string(),
|
||||
hashedHex: z.string(),
|
||||
expiresAt: z.string(),
|
||||
expiresAfterViews: z.number()
|
||||
expiresAfterViews: z.number().min(1).optional(),
|
||||
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -133,19 +147,13 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const { encryptedValue, iv, tag, hashedHex, expiresAt, expiresAfterViews } = req.body;
|
||||
const sharedSecret = await req.server.services.secretSharing.createSharedSecret({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
orgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
encryptedValue,
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt: new Date(expiresAt),
|
||||
expiresAfterViews
|
||||
...req.body
|
||||
});
|
||||
return { id: sharedSecret.id };
|
||||
}
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { OrganizationsSchema, OrgMembershipsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
|
||||
import {
|
||||
OrganizationsSchema,
|
||||
OrgMembershipsSchema,
|
||||
ProjectMembershipsSchema,
|
||||
ProjectsSchema,
|
||||
UserEncryptionKeysSchema,
|
||||
UsersSchema
|
||||
} from "@app/db/schemas";
|
||||
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||
import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
@ -30,6 +37,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
user: UsersSchema.pick({
|
||||
username: true,
|
||||
email: true,
|
||||
isEmailVerified: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
id: true
|
||||
@ -103,6 +111,54 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:organizationId/memberships/:membershipId",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Get organization user membership",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.GET_USER_MEMBERSHIP.organizationId),
|
||||
membershipId: z.string().trim().describe(ORGANIZATIONS.GET_USER_MEMBERSHIP.membershipId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
membership: OrgMembershipsSchema.merge(
|
||||
z.object({
|
||||
user: UsersSchema.pick({
|
||||
username: true,
|
||||
email: true,
|
||||
isEmailVerified: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
id: true
|
||||
}).merge(z.object({ publicKey: z.string().nullable() }))
|
||||
})
|
||||
).omit({ createdAt: true, updatedAt: true })
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const membership = await server.services.org.getOrgMembership({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId,
|
||||
membershipId: req.params.membershipId
|
||||
});
|
||||
return { membership };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/:organizationId/memberships/:membershipId",
|
||||
@ -121,7 +177,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
membershipId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.membershipId)
|
||||
}),
|
||||
body: z.object({
|
||||
role: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.role)
|
||||
role: z.string().trim().optional().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.role),
|
||||
isActive: z.boolean().optional().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.isActive)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -129,17 +186,17 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
if (req.auth.actor !== ActorType.USER) return;
|
||||
|
||||
const membership = await server.services.org.updateOrgMembership({
|
||||
userId: req.permission.id,
|
||||
role: req.body.role,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
orgId: req.params.organizationId,
|
||||
membershipId: req.params.membershipId,
|
||||
actorOrgId: req.permission.orgId
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body
|
||||
});
|
||||
return { membership };
|
||||
}
|
||||
@ -183,6 +240,69 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
// TODO: re-think endpoint structure in future so users only need to pass in membershipId bc organizationId is redundant
|
||||
method: "GET",
|
||||
url: "/:organizationId/memberships/:membershipId/project-memberships",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
description: "Get project memberships given organization membership",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
params: z.object({
|
||||
organizationId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.organizationId),
|
||||
membershipId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.membershipId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
memberships: ProjectMembershipsSchema.extend({
|
||||
user: UsersSchema.pick({
|
||||
email: true,
|
||||
username: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
id: true
|
||||
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
|
||||
project: ProjectsSchema.pick({ name: true, id: 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()
|
||||
})
|
||||
)
|
||||
})
|
||||
.omit({ createdAt: true, updatedAt: true })
|
||||
.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const memberships = await server.services.org.listProjectMembershipsByOrgMembershipId({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
orgId: req.params.organizationId,
|
||||
orgMembershipId: req.params.membershipId
|
||||
});
|
||||
return { memberships };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/",
|
||||
|
@ -317,10 +317,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
slug: slugSchema.describe("The slug of the project to list CAs.")
|
||||
slug: slugSchema.describe(PROJECTS.LIST_CAS.slug)
|
||||
}),
|
||||
querystring: z.object({
|
||||
status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional()
|
||||
status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional().describe(PROJECTS.LIST_CAS.status),
|
||||
friendlyName: z.string().optional().describe(PROJECTS.LIST_CAS.friendlyName),
|
||||
commonName: z.string().optional().describe(PROJECTS.LIST_CAS.commonName),
|
||||
offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CAS.offset),
|
||||
limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CAS.limit)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -336,11 +340,11 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
orgId: req.permission.orgId,
|
||||
type: ProjectFilterType.SLUG
|
||||
},
|
||||
status: req.query.status,
|
||||
actorId: req.permission.id,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actor: req.permission.type
|
||||
actor: req.permission.type,
|
||||
...req.query
|
||||
});
|
||||
return { cas };
|
||||
}
|
||||
@ -354,11 +358,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
slug: slugSchema.describe("The slug of the project to list certificates.")
|
||||
slug: slugSchema.describe(PROJECTS.LIST_CERTIFICATES.slug)
|
||||
}),
|
||||
querystring: z.object({
|
||||
offset: z.coerce.number().min(0).max(100).default(0),
|
||||
limit: z.coerce.number().min(1).max(100).default(25)
|
||||
friendlyName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.friendlyName),
|
||||
commonName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.commonName),
|
||||
offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CERTIFICATES.offset),
|
||||
limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CERTIFICATES.limit)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -8,19 +8,35 @@ export type TCertificateDALFactory = ReturnType<typeof certificateDALFactory>;
|
||||
export const certificateDALFactory = (db: TDbClient) => {
|
||||
const certificateOrm = ormify(db, TableName.Certificate);
|
||||
|
||||
const countCertificatesInProject = async (projectId: string) => {
|
||||
const countCertificatesInProject = async ({
|
||||
projectId,
|
||||
friendlyName,
|
||||
commonName
|
||||
}: {
|
||||
projectId: string;
|
||||
friendlyName?: string;
|
||||
commonName?: string;
|
||||
}) => {
|
||||
try {
|
||||
interface CountResult {
|
||||
count: string;
|
||||
}
|
||||
|
||||
const count = await db
|
||||
let query = db
|
||||
.replicaNode()(TableName.Certificate)
|
||||
.join(TableName.CertificateAuthority, `${TableName.Certificate}.caId`, `${TableName.CertificateAuthority}.id`)
|
||||
.join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`)
|
||||
.where(`${TableName.Project}.id`, projectId)
|
||||
.count("*")
|
||||
.first();
|
||||
.where(`${TableName.Project}.id`, projectId);
|
||||
|
||||
if (friendlyName) {
|
||||
query = query.andWhere(`${TableName.Certificate}.friendlyName`, friendlyName);
|
||||
}
|
||||
|
||||
if (commonName) {
|
||||
query = query.andWhere(`${TableName.Certificate}.commonName`, commonName);
|
||||
}
|
||||
|
||||
const count = await query.count("*").first();
|
||||
|
||||
return parseInt((count as unknown as CountResult).count || "0", 10);
|
||||
} catch (error) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TOrgMembershipDALFactory = ReturnType<typeof orgMembershipDALFactory>;
|
||||
@ -7,7 +8,51 @@ export type TOrgMembershipDALFactory = ReturnType<typeof orgMembershipDALFactory
|
||||
export const orgMembershipDALFactory = (db: TDbClient) => {
|
||||
const orgMembershipOrm = ormify(db, TableName.OrgMembership);
|
||||
|
||||
const findOrgMembershipById = async (membershipId: string) => {
|
||||
try {
|
||||
const member = await db
|
||||
.replicaNode()(TableName.OrgMembership)
|
||||
.where(`${TableName.OrgMembership}.id`, membershipId)
|
||||
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
|
||||
.leftJoin<TUserEncryptionKeys>(
|
||||
TableName.UserEncryptionKey,
|
||||
`${TableName.UserEncryptionKey}.userId`,
|
||||
`${TableName.Users}.id`
|
||||
)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.OrgMembership),
|
||||
db.ref("inviteEmail").withSchema(TableName.OrgMembership),
|
||||
db.ref("orgId").withSchema(TableName.OrgMembership),
|
||||
db.ref("role").withSchema(TableName.OrgMembership),
|
||||
db.ref("roleId").withSchema(TableName.OrgMembership),
|
||||
db.ref("status").withSchema(TableName.OrgMembership),
|
||||
db.ref("isActive").withSchema(TableName.OrgMembership),
|
||||
db.ref("email").withSchema(TableName.Users),
|
||||
db.ref("username").withSchema(TableName.Users),
|
||||
db.ref("firstName").withSchema(TableName.Users),
|
||||
db.ref("lastName").withSchema(TableName.Users),
|
||||
db.ref("isEmailVerified").withSchema(TableName.Users),
|
||||
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||
db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
|
||||
)
|
||||
.where({ isGhost: false }) // MAKE SURE USER IS NOT A GHOST USER
|
||||
.first();
|
||||
|
||||
if (!member) return undefined;
|
||||
|
||||
const { email, isEmailVerified, username, firstName, lastName, userId, publicKey, ...data } = member;
|
||||
|
||||
return {
|
||||
...data,
|
||||
user: { email, isEmailVerified, username, firstName, lastName, id: userId, publicKey }
|
||||
};
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find org membership by id" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...orgMembershipOrm
|
||||
...orgMembershipOrm,
|
||||
findOrgMembershipById
|
||||
};
|
||||
};
|
||||
|
@ -76,6 +76,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
db.ref("status").withSchema(TableName.OrgMembership),
|
||||
db.ref("isActive").withSchema(TableName.OrgMembership),
|
||||
db.ref("email").withSchema(TableName.Users),
|
||||
db.ref("isEmailVerified").withSchema(TableName.Users),
|
||||
db.ref("username").withSchema(TableName.Users),
|
||||
db.ref("firstName").withSchema(TableName.Users),
|
||||
db.ref("lastName").withSchema(TableName.Users),
|
||||
@ -84,9 +85,9 @@ export const orgDALFactory = (db: TDbClient) => {
|
||||
)
|
||||
.where({ isGhost: false }); // MAKE SURE USER IS NOT A GHOST USER
|
||||
|
||||
return members.map(({ email, username, firstName, lastName, userId, publicKey, ...data }) => ({
|
||||
return members.map(({ email, isEmailVerified, username, firstName, lastName, userId, publicKey, ...data }) => ({
|
||||
...data,
|
||||
user: { email, username, firstName, lastName, id: userId, publicKey }
|
||||
user: { email, isEmailVerified, username, firstName, lastName, id: userId, publicKey }
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find all org members" });
|
||||
|
@ -42,6 +42,61 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
|
||||
return role;
|
||||
};
|
||||
|
||||
const getRole = async (
|
||||
userId: string,
|
||||
orgId: string,
|
||||
roleId: string,
|
||||
actorAuthMethod: ActorAuthMethod,
|
||||
actorOrgId: string | undefined
|
||||
) => {
|
||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||
|
||||
switch (roleId) {
|
||||
case "b11b49a9-09a9-4443-916a-4246f9ff2c69": {
|
||||
return {
|
||||
id: roleId,
|
||||
orgId,
|
||||
name: "Admin",
|
||||
slug: "admin",
|
||||
description: "Complete administration access over the organization",
|
||||
permissions: packRules(orgAdminPermissions.rules),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
};
|
||||
}
|
||||
case "b11b49a9-09a9-4443-916a-4246f9ff2c70": {
|
||||
return {
|
||||
id: roleId,
|
||||
orgId,
|
||||
name: "Member",
|
||||
slug: "member",
|
||||
description: "Non-administrative role in an organization",
|
||||
permissions: packRules(orgMemberPermissions.rules),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
};
|
||||
}
|
||||
case "b10d49a9-09a9-4443-916a-4246f9ff2c72": {
|
||||
return {
|
||||
id: "b10d49a9-09a9-4443-916a-4246f9ff2c72", // dummy user for zod validation in response
|
||||
orgId,
|
||||
name: "No Access",
|
||||
slug: "no-access",
|
||||
description: "No access to any resources in the organization",
|
||||
permissions: packRules(orgNoAccessPermissions.rules),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
};
|
||||
}
|
||||
default: {
|
||||
const role = await orgRoleDAL.findOne({ id: roleId, orgId });
|
||||
if (!role) throw new BadRequestError({ message: "Role not found", name: "Get role" });
|
||||
return role;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateRole = async (
|
||||
userId: string,
|
||||
orgId: string,
|
||||
@ -144,5 +199,5 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
|
||||
return { permissions: packRules(permission.rules), membership };
|
||||
};
|
||||
|
||||
return { createRole, updateRole, deleteRole, listRoles, getUserPermission };
|
||||
return { createRole, getRole, updateRole, deleteRole, listRoles, getUserPermission };
|
||||
};
|
||||
|
@ -15,9 +15,10 @@ import { getConfig } from "@app/lib/config/env";
|
||||
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
||||
import { generateSymmetricKey, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
|
||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { isDisposableEmail } from "@app/lib/validator";
|
||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||
|
||||
import { ActorAuthMethod, ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type";
|
||||
@ -38,7 +39,9 @@ import {
|
||||
TFindAllWorkspacesDTO,
|
||||
TFindOrgMembersByEmailDTO,
|
||||
TGetOrgGroupsDTO,
|
||||
TGetOrgMembershipDTO,
|
||||
TInviteUserToOrgDTO,
|
||||
TListProjectMembershipsByOrgMembershipIdDTO,
|
||||
TUpdateOrgDTO,
|
||||
TUpdateOrgMembershipDTO,
|
||||
TVerifyUserToOrgDTO
|
||||
@ -54,6 +57,7 @@ type TOrgServiceFactoryDep = {
|
||||
projectDAL: TProjectDALFactory;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId" | "delete">;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
|
||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOne">;
|
||||
incidentContactDAL: TIncidentContactsDALFactory;
|
||||
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
|
||||
smtpService: TSmtpService;
|
||||
@ -79,6 +83,7 @@ export const orgServiceFactory = ({
|
||||
projectDAL,
|
||||
projectMembershipDAL,
|
||||
projectKeyDAL,
|
||||
orgMembershipDAL,
|
||||
tokenService,
|
||||
orgBotDAL,
|
||||
licenseService,
|
||||
@ -364,6 +369,7 @@ export const orgServiceFactory = ({
|
||||
* */
|
||||
const updateOrgMembership = async ({
|
||||
role,
|
||||
isActive,
|
||||
orgId,
|
||||
userId,
|
||||
membershipId,
|
||||
@ -373,8 +379,16 @@ export const orgServiceFactory = ({
|
||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
|
||||
|
||||
const foundMembership = await orgMembershipDAL.findOne({
|
||||
id: membershipId,
|
||||
orgId
|
||||
});
|
||||
if (!foundMembership) throw new NotFoundError({ message: "Failed to find organization membership" });
|
||||
if (foundMembership.userId === userId)
|
||||
throw new BadRequestError({ message: "Cannot update own organization membership" });
|
||||
|
||||
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
|
||||
if (isCustomRole) {
|
||||
if (role && isCustomRole) {
|
||||
const customRole = await orgRoleDAL.findOne({ slug: role, orgId });
|
||||
if (!customRole) throw new BadRequestError({ name: "Update membership", message: "Role not found" });
|
||||
|
||||
@ -394,7 +408,7 @@ export const orgServiceFactory = ({
|
||||
return membership;
|
||||
}
|
||||
|
||||
const [membership] = await orgDAL.updateMembership({ id: membershipId, orgId }, { role, roleId: null });
|
||||
const [membership] = await orgDAL.updateMembership({ id: membershipId, orgId }, { role, roleId: null, isActive });
|
||||
return membership;
|
||||
};
|
||||
/*
|
||||
@ -585,6 +599,24 @@ export const orgServiceFactory = ({
|
||||
return { token, user };
|
||||
};
|
||||
|
||||
const getOrgMembership = async ({
|
||||
membershipId,
|
||||
orgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TGetOrgMembershipDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||
|
||||
const membership = await orgMembershipDAL.findOrgMembershipById(membershipId);
|
||||
if (!membership) throw new NotFoundError({ message: "Failed to find organization membership" });
|
||||
if (membership.orgId !== orgId) throw new NotFoundError({ message: "Failed to find organization membership" });
|
||||
|
||||
return membership;
|
||||
};
|
||||
|
||||
const deleteOrgMembership = async ({
|
||||
orgId,
|
||||
userId,
|
||||
@ -608,6 +640,26 @@ export const orgServiceFactory = ({
|
||||
return deletedMembership;
|
||||
};
|
||||
|
||||
const listProjectMembershipsByOrgMembershipId = async ({
|
||||
orgMembershipId,
|
||||
orgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TListProjectMembershipsByOrgMembershipIdDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||
|
||||
const membership = await orgMembershipDAL.findOrgMembershipById(orgMembershipId);
|
||||
if (!membership) throw new NotFoundError({ message: "Failed to find organization membership" });
|
||||
if (membership.orgId !== orgId) throw new NotFoundError({ message: "Failed to find organization membership" });
|
||||
|
||||
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, membership.user.id);
|
||||
|
||||
return projectMemberships;
|
||||
};
|
||||
|
||||
/*
|
||||
* CRUD operations of incident contacts
|
||||
* */
|
||||
@ -668,6 +720,7 @@ export const orgServiceFactory = ({
|
||||
findOrgMembersByUsername,
|
||||
createOrganization,
|
||||
deleteOrganizationById,
|
||||
getOrgMembership,
|
||||
deleteOrgMembership,
|
||||
findAllWorkspaces,
|
||||
addGhostUser,
|
||||
@ -676,6 +729,7 @@ export const orgServiceFactory = ({
|
||||
findIncidentContacts,
|
||||
createIncidentContact,
|
||||
deleteIncidentContact,
|
||||
getOrgGroups
|
||||
getOrgGroups,
|
||||
listProjectMembershipsByOrgMembershipId
|
||||
};
|
||||
};
|
||||
|
@ -6,11 +6,16 @@ export type TUpdateOrgMembershipDTO = {
|
||||
userId: string;
|
||||
orgId: string;
|
||||
membershipId: string;
|
||||
role: string;
|
||||
role?: string;
|
||||
isActive?: boolean;
|
||||
actorOrgId: string | undefined;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
};
|
||||
|
||||
export type TGetOrgMembershipDTO = {
|
||||
membershipId: string;
|
||||
} & TOrgPermission;
|
||||
|
||||
export type TDeleteOrgMembershipDTO = {
|
||||
userId: string;
|
||||
orgId: string;
|
||||
@ -55,3 +60,7 @@ export type TUpdateOrgDTO = {
|
||||
} & TOrgPermission;
|
||||
|
||||
export type TGetOrgGroupsDTO = TOrgPermission;
|
||||
|
||||
export type TListProjectMembershipsByOrgMembershipIdDTO = {
|
||||
orgMembershipId: string;
|
||||
} & TOrgPermission;
|
||||
|
@ -24,10 +24,10 @@ export const getBotKeyFnFactory = (
|
||||
|
||||
const bot = await projectBotDAL.findOne({ projectId: project.id });
|
||||
|
||||
if (!bot) throw new BadRequestError({ message: "Failed to find bot key" });
|
||||
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active" });
|
||||
if (!bot) throw new BadRequestError({ message: "Failed to find bot key", name: "bot_not_found_error" });
|
||||
if (!bot.isActive) throw new BadRequestError({ message: "Bot is not active", name: "bot_not_found_error" });
|
||||
if (!bot.encryptedProjectKeyNonce || !bot.encryptedProjectKey)
|
||||
throw new BadRequestError({ message: "Encryption key missing" });
|
||||
throw new BadRequestError({ message: "Encryption key missing", name: "bot_not_found_error" });
|
||||
|
||||
const botPrivateKey = getBotPrivateKey({ bot });
|
||||
|
||||
|
@ -24,10 +24,15 @@ export const projectEnvDALFactory = (db: TDbClient) => {
|
||||
// we are using postion based sorting as its a small list
|
||||
// this will return the last value of the position in a folder with secret imports
|
||||
const findLastEnvPosition = async (projectId: string, tx?: Knex) => {
|
||||
// acquire update lock on project environments.
|
||||
// this ensures that concurrent invocations will wait and execute sequentially
|
||||
await (tx || db)(TableName.Environment).where({ projectId }).forUpdate();
|
||||
|
||||
const lastPos = await (tx || db)(TableName.Environment)
|
||||
.where({ projectId })
|
||||
.max("position", { as: "position" })
|
||||
.first();
|
||||
|
||||
return lastPos?.position || 0;
|
||||
};
|
||||
|
||||
|
@ -3,12 +3,12 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TProjectEnvDALFactory } from "./project-env-dal";
|
||||
import { TCreateEnvDTO, TDeleteEnvDTO, TUpdateEnvDTO } from "./project-env-types";
|
||||
import { TCreateEnvDTO, TDeleteEnvDTO, TGetEnvDTO, TUpdateEnvDTO } from "./project-env-types";
|
||||
|
||||
type TProjectEnvServiceFactoryDep = {
|
||||
projectEnvDAL: TProjectEnvDALFactory;
|
||||
@ -139,9 +139,35 @@ export const projectEnvServiceFactory = ({
|
||||
return env;
|
||||
};
|
||||
|
||||
const getEnvironmentById = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod, id }: TGetEnvDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
||||
|
||||
const [env] = await projectEnvDAL.find({
|
||||
id,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (!env) {
|
||||
throw new NotFoundError({
|
||||
message: "Environment does not exist"
|
||||
});
|
||||
}
|
||||
|
||||
return env;
|
||||
};
|
||||
|
||||
return {
|
||||
createEnvironment,
|
||||
updateEnvironment,
|
||||
deleteEnvironment
|
||||
deleteEnvironment,
|
||||
getEnvironmentById
|
||||
};
|
||||
};
|
||||
|
@ -20,3 +20,7 @@ export type TReorderEnvDTO = {
|
||||
id: string;
|
||||
pos: number;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TGetEnvDTO = {
|
||||
id: string;
|
||||
} & TProjectPermission;
|
||||
|
@ -16,6 +16,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.ProjectMembership)
|
||||
.where({ [`${TableName.ProjectMembership}.projectId` as "projectId"]: projectId })
|
||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||
.where((qb) => {
|
||||
if (filter.usernames) {
|
||||
@ -58,17 +59,22 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
||||
db.ref("isTemporary").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole)
|
||||
db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project)
|
||||
)
|
||||
.where({ isGhost: false });
|
||||
|
||||
const members = sqlNestRelationships({
|
||||
data: docs,
|
||||
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, userId }) => ({
|
||||
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, userId, projectName }) => ({
|
||||
id,
|
||||
userId,
|
||||
projectId,
|
||||
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost }
|
||||
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost },
|
||||
project: {
|
||||
id: projectId,
|
||||
name: projectName
|
||||
}
|
||||
}),
|
||||
key: "id",
|
||||
childrenMapper: [
|
||||
@ -151,14 +157,95 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
|
||||
|
||||
const findProjectMembershipsByUserId = async (orgId: string, userId: string) => {
|
||||
try {
|
||||
const memberships = await db
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.ProjectMembership)
|
||||
.where({ userId })
|
||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||
.where({ [`${TableName.Project}.orgId` as "orgId"]: orgId })
|
||||
.select(selectAllTableCols(TableName.ProjectMembership));
|
||||
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
|
||||
.where(`${TableName.Users}.id`, userId)
|
||||
.where(`${TableName.Project}.orgId`, orgId)
|
||||
.join<TUserEncryptionKeys>(
|
||||
TableName.UserEncryptionKey,
|
||||
`${TableName.UserEncryptionKey}.userId`,
|
||||
`${TableName.Users}.id`
|
||||
)
|
||||
.join(
|
||||
TableName.ProjectUserMembershipRole,
|
||||
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
|
||||
`${TableName.ProjectMembership}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.ProjectRoles,
|
||||
`${TableName.ProjectUserMembershipRole}.customRoleId`,
|
||||
`${TableName.ProjectRoles}.id`
|
||||
)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.ProjectMembership),
|
||||
db.ref("isGhost").withSchema(TableName.Users),
|
||||
db.ref("username").withSchema(TableName.Users),
|
||||
db.ref("email").withSchema(TableName.Users),
|
||||
db.ref("publicKey").withSchema(TableName.UserEncryptionKey),
|
||||
db.ref("firstName").withSchema(TableName.Users),
|
||||
db.ref("lastName").withSchema(TableName.Users),
|
||||
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||
db.ref("role").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("id").withSchema(TableName.ProjectUserMembershipRole).as("membershipRoleId"),
|
||||
db.ref("customRoleId").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("name").withSchema(TableName.ProjectRoles).as("customRoleName"),
|
||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||
db.ref("temporaryMode").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("isTemporary").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("id").as("projectId").withSchema(TableName.Project)
|
||||
)
|
||||
.where({ isGhost: false });
|
||||
|
||||
return memberships;
|
||||
const members = sqlNestRelationships({
|
||||
data: docs,
|
||||
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, projectId, projectName }) => ({
|
||||
id,
|
||||
userId,
|
||||
projectId,
|
||||
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost },
|
||||
project: {
|
||||
id: projectId,
|
||||
name: projectName
|
||||
}
|
||||
}),
|
||||
key: "id",
|
||||
childrenMapper: [
|
||||
{
|
||||
label: "roles" as const,
|
||||
key: "membershipRoleId",
|
||||
mapper: ({
|
||||
role,
|
||||
customRoleId,
|
||||
customRoleName,
|
||||
customRoleSlug,
|
||||
membershipRoleId,
|
||||
temporaryRange,
|
||||
temporaryMode,
|
||||
temporaryAccessEndTime,
|
||||
temporaryAccessStartTime,
|
||||
isTemporary
|
||||
}) => ({
|
||||
id: membershipRoleId,
|
||||
role,
|
||||
customRoleId,
|
||||
customRoleName,
|
||||
customRoleSlug,
|
||||
temporaryRange,
|
||||
temporaryMode,
|
||||
temporaryAccessEndTime,
|
||||
temporaryAccessStartTime,
|
||||
isTemporary
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
return members;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find project memberships by user id" });
|
||||
}
|
||||
|
@ -162,12 +162,19 @@ export const projectRoleServiceFactory = ({
|
||||
actorOrgId
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
|
||||
|
||||
if (data?.slug) {
|
||||
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
|
||||
if (existingRole && existingRole.id !== roleId)
|
||||
throw new BadRequestError({ name: "Update Role", message: "Duplicate role" });
|
||||
}
|
||||
const [updatedRole] = await projectRoleDAL.update({ id: roleId, projectId }, data);
|
||||
const [updatedRole] = await projectRoleDAL.update(
|
||||
{ id: roleId, projectId },
|
||||
{
|
||||
...data,
|
||||
permissions: data.permissions ? data.permissions : undefined
|
||||
}
|
||||
);
|
||||
if (!updatedRole) throw new BadRequestError({ message: "Role not found", name: "Update role" });
|
||||
return { ...updatedRole, permissions: unpackPermissions(updatedRole.permissions) };
|
||||
};
|
||||
|
@ -575,6 +575,10 @@ export const projectServiceFactory = ({
|
||||
*/
|
||||
const listProjectCas = async ({
|
||||
status,
|
||||
friendlyName,
|
||||
commonName,
|
||||
limit = 25,
|
||||
offset = 0,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
@ -596,10 +600,15 @@ export const projectServiceFactory = ({
|
||||
ProjectPermissionSub.CertificateAuthorities
|
||||
);
|
||||
|
||||
const cas = await certificateAuthorityDAL.find({
|
||||
projectId: project.id,
|
||||
...(status && { status })
|
||||
});
|
||||
const cas = await certificateAuthorityDAL.find(
|
||||
{
|
||||
projectId: project.id,
|
||||
...(status && { status }),
|
||||
...(friendlyName && { friendlyName }),
|
||||
...(commonName && { commonName })
|
||||
},
|
||||
{ offset, limit, sort: [["updatedAt", "desc"]] }
|
||||
);
|
||||
|
||||
return cas;
|
||||
};
|
||||
@ -608,8 +617,10 @@ export const projectServiceFactory = ({
|
||||
* Return list of certificates for project
|
||||
*/
|
||||
const listProjectCertificates = async ({
|
||||
offset,
|
||||
limit,
|
||||
limit = 25,
|
||||
offset = 0,
|
||||
friendlyName,
|
||||
commonName,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
@ -634,12 +645,18 @@ export const projectServiceFactory = ({
|
||||
{
|
||||
$in: {
|
||||
caId: cas.map((ca) => ca.id)
|
||||
}
|
||||
},
|
||||
...(friendlyName && { friendlyName }),
|
||||
...(commonName && { commonName })
|
||||
},
|
||||
{ offset, limit, sort: [["updatedAt", "desc"]] }
|
||||
);
|
||||
|
||||
const count = await certificateDAL.countCertificatesInProject(project.id);
|
||||
const count = await certificateDAL.countCertificatesInProject({
|
||||
projectId: project.id,
|
||||
friendlyName,
|
||||
commonName
|
||||
});
|
||||
|
||||
return {
|
||||
certificates,
|
||||
|
@ -89,6 +89,10 @@ export type AddUserToWsDTO = {
|
||||
|
||||
export type TListProjectCasDTO = {
|
||||
status?: CaStatus;
|
||||
friendlyName?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
commonName?: string;
|
||||
filter: Filter;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
@ -96,4 +100,6 @@ export type TListProjectCertsDTO = {
|
||||
filter: Filter;
|
||||
offset: number;
|
||||
limit: number;
|
||||
friendlyName?: string;
|
||||
commonName?: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
@ -90,7 +90,7 @@ export const fnSecretsFromImports = async ({
|
||||
const secretsFromdeeperImportGroupedByFolderId = groupBy(secretsFromDeeperImports, (i) => i.importFolderId);
|
||||
|
||||
const secrets = allowedImports.map(({ importPath, importEnv, id, folderId }, i) => {
|
||||
const sourceImportFolder = importedFolderGroupBySourceImport[`${importEnv.id}-${importPath}`][0];
|
||||
const sourceImportFolder = importedFolderGroupBySourceImport?.[`${importEnv.id}-${importPath}`]?.[0];
|
||||
const folderDeeperImportSecrets =
|
||||
secretsFromdeeperImportGroupedByFolderId?.[sourceImportFolder?.id || ""]?.[0]?.secrets || [];
|
||||
|
||||
|
@ -10,6 +10,25 @@ export type TSecretSharingDALFactory = ReturnType<typeof secretSharingDALFactory
|
||||
export const secretSharingDALFactory = (db: TDbClient) => {
|
||||
const sharedSecretOrm = ormify(db, TableName.SecretSharing);
|
||||
|
||||
const countAllUserOrgSharedSecrets = async ({ orgId, userId }: { orgId: string; userId: string }) => {
|
||||
try {
|
||||
interface CountResult {
|
||||
count: string;
|
||||
}
|
||||
|
||||
const count = await db
|
||||
.replicaNode()(TableName.SecretSharing)
|
||||
.where(`${TableName.SecretSharing}.orgId`, orgId)
|
||||
.where(`${TableName.SecretSharing}.userId`, userId)
|
||||
.count("*")
|
||||
.first();
|
||||
|
||||
return parseInt((count as unknown as CountResult).count || "0", 10);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Count all user-org shared secrets" });
|
||||
}
|
||||
};
|
||||
|
||||
const pruneExpiredSharedSecrets = async (tx?: Knex) => {
|
||||
try {
|
||||
const today = new Date();
|
||||
@ -19,8 +38,7 @@ export const secretSharingDALFactory = (db: TDbClient) => {
|
||||
.update({
|
||||
encryptedValue: "",
|
||||
tag: "",
|
||||
iv: "",
|
||||
hashedHex: ""
|
||||
iv: ""
|
||||
});
|
||||
return docs;
|
||||
} catch (error) {
|
||||
@ -50,8 +68,7 @@ export const secretSharingDALFactory = (db: TDbClient) => {
|
||||
await sharedSecretOrm.updateById(id, {
|
||||
encryptedValue: "",
|
||||
iv: "",
|
||||
tag: "",
|
||||
hashedHex: ""
|
||||
tag: ""
|
||||
});
|
||||
} catch (error) {
|
||||
throw new DatabaseError({
|
||||
@ -63,6 +80,7 @@ export const secretSharingDALFactory = (db: TDbClient) => {
|
||||
|
||||
return {
|
||||
...sharedSecretOrm,
|
||||
countAllUserOrgSharedSecrets,
|
||||
pruneExpiredSharedSecrets,
|
||||
softDeleteById,
|
||||
findActiveSharedSecrets
|
||||
|
@ -1,39 +1,45 @@
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { SecretSharingAccessType } from "@app/lib/types";
|
||||
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
import { TSecretSharingDALFactory } from "./secret-sharing-dal";
|
||||
import {
|
||||
TCreatePublicSharedSecretDTO,
|
||||
TCreateSharedSecretDTO,
|
||||
TDeleteSharedSecretDTO,
|
||||
TSharedSecretPermission
|
||||
TGetActiveSharedSecretByIdDTO,
|
||||
TGetSharedSecretsDTO
|
||||
} from "./secret-sharing-types";
|
||||
|
||||
type TSecretSharingServiceFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
secretSharingDAL: TSecretSharingDALFactory;
|
||||
orgDAL: TOrgDALFactory;
|
||||
};
|
||||
|
||||
export type TSecretSharingServiceFactory = ReturnType<typeof secretSharingServiceFactory>;
|
||||
|
||||
export const secretSharingServiceFactory = ({
|
||||
permissionService,
|
||||
secretSharingDAL
|
||||
secretSharingDAL,
|
||||
orgDAL
|
||||
}: TSecretSharingServiceFactoryDep) => {
|
||||
const createSharedSecret = async (createSharedSecretInput: TCreateSharedSecretDTO) => {
|
||||
const {
|
||||
actor,
|
||||
actorId,
|
||||
orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
encryptedValue,
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt,
|
||||
expiresAfterViews
|
||||
} = createSharedSecretInput;
|
||||
const createSharedSecret = async ({
|
||||
actor,
|
||||
actorId,
|
||||
orgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
encryptedValue,
|
||||
hashedHex,
|
||||
iv,
|
||||
tag,
|
||||
name,
|
||||
accessType,
|
||||
expiresAt,
|
||||
expiresAfterViews
|
||||
}: TCreateSharedSecretDTO) => {
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
if (!permission) throw new UnauthorizedError({ name: "User not in org" });
|
||||
|
||||
@ -55,20 +61,30 @@ export const secretSharingServiceFactory = ({
|
||||
}
|
||||
|
||||
const newSharedSecret = await secretSharingDAL.create({
|
||||
name,
|
||||
encryptedValue,
|
||||
hashedHex,
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt,
|
||||
expiresAt: new Date(expiresAt),
|
||||
expiresAfterViews,
|
||||
userId: actorId,
|
||||
orgId
|
||||
orgId,
|
||||
accessType
|
||||
});
|
||||
|
||||
return { id: newSharedSecret.id };
|
||||
};
|
||||
|
||||
const createPublicSharedSecret = async (createSharedSecretInput: TCreatePublicSharedSecretDTO) => {
|
||||
const { encryptedValue, iv, tag, hashedHex, expiresAt, expiresAfterViews } = createSharedSecretInput;
|
||||
const createPublicSharedSecret = async ({
|
||||
encryptedValue,
|
||||
hashedHex,
|
||||
iv,
|
||||
tag,
|
||||
expiresAt,
|
||||
expiresAfterViews,
|
||||
accessType
|
||||
}: TCreatePublicSharedSecretDTO) => {
|
||||
if (new Date(expiresAt) < new Date()) {
|
||||
throw new BadRequestError({ message: "Expiration date cannot be in the past" });
|
||||
}
|
||||
@ -88,37 +104,103 @@ export const secretSharingServiceFactory = ({
|
||||
|
||||
const newSharedSecret = await secretSharingDAL.create({
|
||||
encryptedValue,
|
||||
hashedHex,
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt,
|
||||
expiresAfterViews
|
||||
expiresAt: new Date(expiresAt),
|
||||
expiresAfterViews,
|
||||
accessType
|
||||
});
|
||||
return { id: newSharedSecret.id };
|
||||
};
|
||||
|
||||
const getSharedSecrets = async (getSharedSecretsInput: TSharedSecretPermission) => {
|
||||
const { actor, actorId, orgId, actorAuthMethod, actorOrgId } = getSharedSecretsInput;
|
||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||
const getSharedSecrets = async ({
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
offset,
|
||||
limit
|
||||
}: TGetSharedSecretsDTO) => {
|
||||
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
if (!permission) throw new UnauthorizedError({ name: "User not in org" });
|
||||
const userSharedSecrets = await secretSharingDAL.findActiveSharedSecrets({ userId: actorId, orgId });
|
||||
return userSharedSecrets;
|
||||
|
||||
const secrets = await secretSharingDAL.find(
|
||||
{
|
||||
userId: actorId,
|
||||
orgId: actorOrgId
|
||||
},
|
||||
{ offset, limit, sort: [["createdAt", "desc"]] }
|
||||
);
|
||||
|
||||
const count = await secretSharingDAL.countAllUserOrgSharedSecrets({
|
||||
orgId: actorOrgId,
|
||||
userId: actorId
|
||||
});
|
||||
|
||||
return {
|
||||
secrets,
|
||||
totalCount: count
|
||||
};
|
||||
};
|
||||
|
||||
const getActiveSharedSecretByIdAndHashedHex = async (sharedSecretId: string, hashedHex: string) => {
|
||||
const sharedSecret = await secretSharingDAL.findOne({ id: sharedSecretId, hashedHex });
|
||||
if (!sharedSecret) return;
|
||||
if (sharedSecret.expiresAt && sharedSecret.expiresAt < new Date()) {
|
||||
return;
|
||||
const getActiveSharedSecretById = async ({ sharedSecretId, hashedHex, orgId }: TGetActiveSharedSecretByIdDTO) => {
|
||||
const sharedSecret = await secretSharingDAL.findOne({
|
||||
id: sharedSecretId,
|
||||
hashedHex
|
||||
});
|
||||
if (!sharedSecret)
|
||||
throw new NotFoundError({
|
||||
message: "Shared secret not found"
|
||||
});
|
||||
|
||||
const { accessType, expiresAt, expiresAfterViews } = sharedSecret;
|
||||
|
||||
const orgName = sharedSecret.orgId ? (await orgDAL.findOrgById(sharedSecret.orgId))?.name : "";
|
||||
|
||||
if (accessType === SecretSharingAccessType.Organization && orgId !== sharedSecret.orgId)
|
||||
throw new UnauthorizedError();
|
||||
|
||||
if (expiresAt !== null && expiresAt < new Date()) {
|
||||
// check lifetime expiry
|
||||
await secretSharingDAL.softDeleteById(sharedSecretId);
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Access denied: Secret has expired by lifetime"
|
||||
});
|
||||
}
|
||||
if (sharedSecret.expiresAfterViews != null && sharedSecret.expiresAfterViews >= 0) {
|
||||
if (sharedSecret.expiresAfterViews === 0) {
|
||||
await secretSharingDAL.softDeleteById(sharedSecretId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (expiresAfterViews !== null && expiresAfterViews === 0) {
|
||||
// check view count expiry
|
||||
await secretSharingDAL.softDeleteById(sharedSecretId);
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Access denied: Secret has expired by view count"
|
||||
});
|
||||
}
|
||||
|
||||
if (expiresAfterViews) {
|
||||
// decrement view count if view count expiry set
|
||||
await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } });
|
||||
}
|
||||
return sharedSecret;
|
||||
|
||||
await secretSharingDAL.updateById(sharedSecretId, {
|
||||
lastViewedAt: new Date()
|
||||
});
|
||||
|
||||
return {
|
||||
...sharedSecret,
|
||||
orgName:
|
||||
sharedSecret.accessType === SecretSharingAccessType.Organization && orgId === sharedSecret.orgId
|
||||
? orgName
|
||||
: undefined
|
||||
};
|
||||
};
|
||||
|
||||
const deleteSharedSecretById = async (deleteSharedSecretInput: TDeleteSharedSecretDTO) => {
|
||||
@ -134,6 +216,6 @@ export const secretSharingServiceFactory = ({
|
||||
createPublicSharedSecret,
|
||||
getSharedSecrets,
|
||||
deleteSharedSecretById,
|
||||
getActiveSharedSecretByIdAndHashedHex
|
||||
getActiveSharedSecretById
|
||||
};
|
||||
};
|
||||
|
@ -1,20 +1,36 @@
|
||||
import { SecretSharingAccessType, TGenericPermission } from "@app/lib/types";
|
||||
|
||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||
|
||||
export type TGetSharedSecretsDTO = {
|
||||
offset: number;
|
||||
limit: number;
|
||||
} & TGenericPermission;
|
||||
|
||||
export type TSharedSecretPermission = {
|
||||
actor: ActorType;
|
||||
actorId: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId: string;
|
||||
orgId: string;
|
||||
accessType?: SecretSharingAccessType;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type TCreatePublicSharedSecretDTO = {
|
||||
encryptedValue: string;
|
||||
hashedHex: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
expiresAt: string;
|
||||
expiresAfterViews?: number;
|
||||
accessType: SecretSharingAccessType;
|
||||
};
|
||||
|
||||
export type TGetActiveSharedSecretByIdDTO = {
|
||||
sharedSecretId: string;
|
||||
hashedHex: string;
|
||||
expiresAt: Date;
|
||||
expiresAfterViews: number;
|
||||
orgId?: string;
|
||||
};
|
||||
|
||||
export type TCreateSharedSecretDTO = TSharedSecretPermission & TCreatePublicSharedSecretDTO;
|
||||
|
@ -5,6 +5,7 @@ import handlebars from "handlebars";
|
||||
import { createTransport } from "nodemailer";
|
||||
import SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
export type TSmtpConfig = SMTPTransport.Options;
|
||||
@ -12,7 +13,7 @@ export type TSmtpSendMail = {
|
||||
template: SmtpTemplates;
|
||||
subjectLine: string;
|
||||
recipients: string[];
|
||||
substitutions: unknown;
|
||||
substitutions: object;
|
||||
};
|
||||
export type TSmtpService = ReturnType<typeof smtpServiceFactory>;
|
||||
|
||||
@ -23,6 +24,7 @@ export enum SmtpTemplates {
|
||||
EmailMfa = "emailMfa.handlebars",
|
||||
UnlockAccount = "unlockAccount.handlebars",
|
||||
AccessApprovalRequest = "accessApprovalRequest.handlebars",
|
||||
AccessSecretRequestBypassed = "accessSecretRequestBypassed.handlebars",
|
||||
HistoricalSecretList = "historicalSecretLeakIncident.handlebars",
|
||||
NewDeviceJoin = "newDevice.handlebars",
|
||||
OrgInvite = "organizationInvitation.handlebars",
|
||||
@ -46,9 +48,11 @@ export const smtpServiceFactory = (cfg: TSmtpConfig) => {
|
||||
const isSmtpOn = Boolean(cfg.host);
|
||||
|
||||
const sendMail = async ({ substitutions, recipients, template, subjectLine }: TSmtpSendMail) => {
|
||||
const appCfg = getConfig();
|
||||
const html = await fs.readFile(path.resolve(__dirname, "./templates/", template), "utf8");
|
||||
const temp = handlebars.compile(html);
|
||||
const htmlToSend = temp(substitutions);
|
||||
const htmlToSend = temp({ isCloud: appCfg.isCloud, siteUrl: appCfg.SITE_URL, ...substitutions });
|
||||
|
||||
if (isSmtpOn) {
|
||||
await smtp.sendMail({
|
||||
from: cfg.from,
|
||||
|
@ -0,0 +1,28 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Secret Approval Request Policy Bypassed</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Infisical</h1>
|
||||
<h2>Secret Approval Request Bypassed</h2>
|
||||
<p>A secret approval request has been bypassed in the project "{{projectName}}".</p>
|
||||
|
||||
<p>
|
||||
{{requesterFullName}} ({{requesterEmail}}) has merged
|
||||
a secret to environment {{environment}} at secret path {{secretPath}}
|
||||
without obtaining the required approvals.
|
||||
</p>
|
||||
<p>
|
||||
The following reason was provided for bypassing the policy:
|
||||
<em>{{bypassReason}}</em>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To review this action, please visit the request panel
|
||||
<a href="{{approvalUrl}}">here</a>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@ -1,19 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>MFA Code</title>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<h2>Infisical</h2>
|
||||
<h2>Sign in attempt requires further verification</h2>
|
||||
<p>Your MFA code is below — enter it where you started signing in to Infisical.</p>
|
||||
<h2>{{code}}</h2>
|
||||
<p>The MFA code will be valid for 2 minutes.</p>
|
||||
<p>Not you? Contact Infisical or your administrator immediately.</p>
|
||||
</body>
|
||||
<p>Not you? Contact {{#if isCloud}}Infisical{{else}}your administrator{{/if}} immediately.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -9,12 +9,12 @@
|
||||
|
||||
<body>
|
||||
<h3>Infisical has uncovered {{numberOfSecrets}} secret(s) from historical commits to your repo</h3>
|
||||
<p><a href="https://app.infisical.com/secret-scanning"><strong>View leaked secrets</strong></a></p>
|
||||
<p><a href="{{siteUrl}}/secret-scanning"><strong>View leaked secrets</strong></a></p>
|
||||
|
||||
<p>If these are production secrets, please rotate them immediately.</p>
|
||||
|
||||
<p>Once you have taken action, be sure to update the status of the risk in your <a
|
||||
href="https://app.infisical.com/">Infisical
|
||||
href="{{siteUrl}}">Infisical
|
||||
dashboard</a>.</p>
|
||||
</body>
|
||||
|
||||
|
@ -1,19 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Successful login for {{email}} from new device</title>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<h2>Infisical</h2>
|
||||
<p>We're verifying a recent login for {{email}}:</p>
|
||||
<p><strong>Timestamp</strong>: {{timestamp}}</p>
|
||||
<p><strong>IP address</strong>: {{ip}}</p>
|
||||
<p><strong>User agent</strong>: {{userAgent}}</p>
|
||||
<p>If you believe that this login is suspicious, please contact Infisical or reset your password immediately.</p>
|
||||
</body>
|
||||
<p>If you believe that this login is suspicious, please contact {{#if isCloud}}Infisical{{else}}your administrator{{/if}} or reset your password immediately.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -9,6 +9,6 @@
|
||||
<h2>Reset your password</h2>
|
||||
<p>Someone requested a password reset.</p>
|
||||
<a href="{{callback_url}}?token={{token}}&to={{email}}">Reset password</a>
|
||||
<p>If you didn't initiate this request, please contact us immediately at team@infisical.com</p>
|
||||
<p>If you didn't initiate this request, please contact {{#if isCloud}}us immediately at team@infisical.com.{{else}}your administrator immediately.{{/if}}</p>
|
||||
</body>
|
||||
</html>
|
@ -9,7 +9,7 @@
|
||||
|
||||
<body>
|
||||
<h3>Infisical has uncovered {{numberOfSecrets}} secret(s) from your recent push</h3>
|
||||
<p><a href="https://app.infisical.com/secret-scanning"><strong>View leaked secrets</strong></a></p>
|
||||
<p><a href="{{siteUrl}}/secret-scanning"><strong>View leaked secrets</strong></a></p>
|
||||
<p>You are receiving this notification because one or more secret leaks have been detected in a recent commit pushed
|
||||
by {{pusher_name}} ({{pusher_email}}). If
|
||||
these are test secrets, please add `infisical-scan:ignore` at the end of the line containing the secret as comment
|
||||
@ -18,7 +18,7 @@
|
||||
<p>If these are production secrets, please rotate them immediately.</p>
|
||||
|
||||
<p>Once you have taken action, be sure to update the status of the risk in your <a
|
||||
href="https://app.infisical.com/">Infisical
|
||||
href="{{siteUrl}}">Infisical
|
||||
dashboard</a>.</p>
|
||||
</body>
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
<h2>Confirm your email address</h2>
|
||||
<p>Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.</p>
|
||||
<h1>{{code}}</h1>
|
||||
<p>Questions about setting up Infisical? Email us at support@infisical.com</p>
|
||||
<p>Questions about setting up Infisical? {{#if isCloud}}Email us at support@infisical.com{{else}}Contact your administrator{{/if}}.</p>
|
||||
</body>
|
||||
|
||||
</html>
|
42
cli/go.mod
@ -4,36 +4,36 @@ go 1.21
|
||||
|
||||
require (
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0
|
||||
github.com/charmbracelet/lipgloss v0.5.0
|
||||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/chzyer/readline v1.5.1
|
||||
github.com/creack/pty v1.1.21
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/fatih/semgroup v1.2.0
|
||||
github.com/gitleaks/go-gitdiff v0.8.0
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/infisical/go-sdk v0.3.0
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/infisical/go-sdk v0.3.3
|
||||
github.com/mattn/go-isatty v0.0.18
|
||||
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
||||
github.com/muesli/mango-cobra v1.2.0
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/roff v0.1.0
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a
|
||||
github.com/rs/cors v1.11.0
|
||||
github.com/rs/zerolog v1.26.1
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/term v0.20.0
|
||||
golang.org/x/crypto v0.25.0
|
||||
golang.org/x/term v0.22.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.5.1 // indirect
|
||||
cloud.google.com/go/auth v0.7.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.4.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.11 // indirect
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect
|
||||
@ -49,7 +49,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/danieljoos/wincred v1.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
|
||||
@ -64,17 +64,17 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/muesli/mango v0.1.0 // indirect
|
||||
github.com/muesli/mango-pflag v0.1.0 // indirect
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@ -91,17 +91,17 @@ require (
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/api v0.183.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
google.golang.org/api v0.188.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect
|
||||
google.golang.org/grpc v1.64.1 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
79
cli/go.sum
@ -18,8 +18,8 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw=
|
||||
cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s=
|
||||
cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts=
|
||||
cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
@ -28,13 +28,13 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c=
|
||||
cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
||||
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
||||
cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw=
|
||||
cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
@ -83,13 +83,15 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8=
|
||||
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
@ -231,8 +233,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
@ -263,8 +265,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/infisical/go-sdk v0.3.0 h1:Ls71t227F4CWVQWdStcwv8WDyfHe8eRlyAuMRNHsmlQ=
|
||||
github.com/infisical/go-sdk v0.3.0/go.mod h1:vHTDVw3k+wfStXab513TGk1n53kaKF2xgLqpw/xvtl4=
|
||||
github.com/infisical/go-sdk v0.3.3 h1:TE2WNMmiDej+TCkPKgHk3h8zVlEQUtM5rz8ouVnXTcU=
|
||||
github.com/infisical/go-sdk v0.3.3/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@ -292,13 +294,12 @@ github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69Y
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@ -324,13 +325,12 @@ github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbY
|
||||
github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA=
|
||||
github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg=
|
||||
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
|
||||
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
@ -340,8 +340,6 @@ github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5d
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8=
|
||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
@ -455,8 +453,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -536,8 +535,9 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -612,24 +612,26 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -642,8 +644,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -729,8 +732,8 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE=
|
||||
google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ=
|
||||
google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw=
|
||||
google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@ -779,10 +782,10 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e h1:SkdGTrROJl2jRGT/Fxv5QUf9jtdKCQh4KQJXbXVLAi0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240521202816-d264139d666e/go.mod h1:LweJcLbyVij6rCex8YunD8DYR5VDonap/jYl3ZRxcIU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@ -803,8 +806,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -817,8 +820,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -225,25 +225,6 @@ func CallIsAuthenticated(httpClient *resty.Client) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func CallGetAccessibleEnvironments(httpClient *resty.Client, request GetAccessibleEnvironmentsRequest) (GetAccessibleEnvironmentsResponse, error) {
|
||||
var accessibleEnvironmentsResponse GetAccessibleEnvironmentsResponse
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetResult(&accessibleEnvironmentsResponse).
|
||||
SetHeader("User-Agent", USER_AGENT).
|
||||
Get(fmt.Sprintf("%v/v2/workspace/%s/environments", config.INFISICAL_URL, request.WorkspaceId))
|
||||
|
||||
if err != nil {
|
||||
return GetAccessibleEnvironmentsResponse{}, err
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetAccessibleEnvironmentsResponse{}, fmt.Errorf("CallGetAccessibleEnvironments: Unsuccessful response: [response=%v] [response-code=%v] [url=%s]", response, response.StatusCode(), response.Request.URL)
|
||||
}
|
||||
|
||||
return accessibleEnvironmentsResponse, nil
|
||||
}
|
||||
|
||||
func CallGetNewAccessTokenWithRefreshToken(httpClient *resty.Client, refreshToken string) (GetNewAccessTokenWithRefreshTokenResponse, error) {
|
||||
var newAccessToken GetNewAccessTokenWithRefreshTokenResponse
|
||||
response, err := httpClient.
|
||||
@ -267,45 +248,6 @@ func CallGetNewAccessTokenWithRefreshToken(httpClient *resty.Client, refreshToke
|
||||
return newAccessToken, nil
|
||||
}
|
||||
|
||||
func CallGetSecretsV3(httpClient *resty.Client, request GetEncryptedSecretsV3Request) (GetEncryptedSecretsV3Response, error) {
|
||||
var secretsResponse GetEncryptedSecretsV3Response
|
||||
|
||||
httpRequest := httpClient.
|
||||
R().
|
||||
SetResult(&secretsResponse).
|
||||
SetHeader("User-Agent", USER_AGENT).
|
||||
SetQueryParam("environment", request.Environment).
|
||||
SetQueryParam("workspaceId", request.WorkspaceId)
|
||||
|
||||
if request.Recursive {
|
||||
httpRequest.SetQueryParam("recursive", "true")
|
||||
}
|
||||
|
||||
if request.IncludeImport {
|
||||
httpRequest.SetQueryParam("include_imports", "true")
|
||||
}
|
||||
|
||||
if request.SecretPath != "" {
|
||||
httpRequest.SetQueryParam("secretPath", request.SecretPath)
|
||||
}
|
||||
|
||||
response, err := httpRequest.Get(fmt.Sprintf("%v/v3/secrets", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Unable to complete api request [err=%s]", err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
if response.StatusCode() == 401 {
|
||||
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Request to access secrets with [environment=%v] [path=%v] [workspaceId=%v] is denied. Please check if your authentication method has access to requested scope", request.Environment, request.SecretPath, request.WorkspaceId)
|
||||
} else {
|
||||
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%v]", response.RawResponse)
|
||||
}
|
||||
}
|
||||
|
||||
return secretsResponse, nil
|
||||
}
|
||||
|
||||
func CallGetFoldersV1(httpClient *resty.Client, request GetFoldersV1Request) (GetFoldersV1Response, error) {
|
||||
var foldersResponse GetFoldersV1Response
|
||||
httpRequest := httpClient.
|
||||
@ -370,27 +312,7 @@ func CallDeleteFolderV1(httpClient *resty.Client, request DeleteFolderV1Request)
|
||||
return folderResponse, nil
|
||||
}
|
||||
|
||||
func CallCreateSecretsV3(httpClient *resty.Client, request CreateSecretV3Request) error {
|
||||
var secretsResponse GetEncryptedSecretsV3Response
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetResult(&secretsResponse).
|
||||
SetHeader("User-Agent", USER_AGENT).
|
||||
SetBody(request).
|
||||
Post(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("CallCreateSecretsV3: Unable to complete api request [err=%s]", err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return fmt.Errorf("CallCreateSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request) error {
|
||||
func CallDeleteSecretsRawV3(httpClient *resty.Client, request DeleteSecretV3Request) error {
|
||||
|
||||
var secretsResponse GetEncryptedSecretsV3Response
|
||||
response, err := httpClient.
|
||||
@ -398,7 +320,7 @@ func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request
|
||||
SetResult(&secretsResponse).
|
||||
SetHeader("User-Agent", USER_AGENT).
|
||||
SetBody(request).
|
||||
Delete(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
|
||||
Delete(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("CallDeleteSecretsV3: Unable to complete api request [err=%s]", err)
|
||||
@ -411,46 +333,6 @@ func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request
|
||||
return nil
|
||||
}
|
||||
|
||||
func CallUpdateSecretsV3(httpClient *resty.Client, request UpdateSecretByNameV3Request, secretName string) error {
|
||||
var secretsResponse GetEncryptedSecretsV3Response
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetResult(&secretsResponse).
|
||||
SetHeader("User-Agent", USER_AGENT).
|
||||
SetBody(request).
|
||||
Patch(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, secretName))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("CallUpdateSecretsV3: Unable to complete api request [err=%s]", err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return fmt.Errorf("CallUpdateSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CallGetSingleSecretByNameV3(httpClient *resty.Client, request CreateSecretV3Request) error {
|
||||
var secretsResponse GetEncryptedSecretsV3Response
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetResult(&secretsResponse).
|
||||
SetHeader("User-Agent", USER_AGENT).
|
||||
SetBody(request).
|
||||
Post(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("CallGetSingleSecretByNameV3: Unable to complete api request [err=%s]", err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return fmt.Errorf("CallGetSingleSecretByNameV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CallCreateServiceToken(httpClient *resty.Client, request CreateServiceTokenRequest) (CreateServiceTokenResponse, error) {
|
||||
var createServiceTokenResponse CreateServiceTokenResponse
|
||||
response, err := httpClient.
|
||||
@ -535,8 +417,12 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unable to complete api request [err=%w]", err)
|
||||
}
|
||||
|
||||
if response.IsError() && strings.Contains(response.String(), "bot_not_found_error") {
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf("project with id %s is a legacy project type, please navigate to project settings and disable end to end encryption then try again", request.WorkspaceId)
|
||||
if response.IsError() &&
|
||||
(strings.Contains(response.String(), "bot_not_found_error") ||
|
||||
strings.Contains(strings.ToLower(response.String()), "failed to find bot key") ||
|
||||
strings.Contains(strings.ToLower(response.String()), "bot is not active")) {
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf(`Project with id %s is incompatible with your current CLI version. Upgrade your project by visiting the project settings page. If you're self hosting and project upgrade option isn't yet available, contact your administrator to upgrade your Infisical instance to the latest release.
|
||||
`, request.WorkspaceId)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
|
@ -312,7 +312,7 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {
|
||||
|
||||
func secretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string) ([]models.SingleEnvironmentVariable, error) {
|
||||
return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) {
|
||||
res, err := util.GetPlainTextSecretsViaMachineIdentity(accessToken, projectID, envSlug, secretPath, false, false)
|
||||
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -550,7 +550,7 @@ func (tm *AgentManager) FetchAzureAuthAccessToken() (credential infisicalSdk.Mac
|
||||
return infisicalSdk.MachineIdentityCredential{}, fmt.Errorf("unable to get identity id: %v", err)
|
||||
}
|
||||
|
||||
return tm.infisicalClient.Auth().AzureAuthLogin(identityId)
|
||||
return tm.infisicalClient.Auth().AzureAuthLogin(identityId, "")
|
||||
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/pkg/browser"
|
||||
"github.com/posthog/posthog-go"
|
||||
"github.com/rs/cors"
|
||||
"github.com/rs/zerolog/log"
|
||||
@ -84,7 +83,7 @@ func handleAzureAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.Infis
|
||||
return infisicalSdk.MachineIdentityCredential{}, err
|
||||
}
|
||||
|
||||
return infisicalClient.Auth().AzureAuthLogin(identityId)
|
||||
return infisicalClient.Auth().AzureAuthLogin(identityId, "")
|
||||
}
|
||||
|
||||
func handleGcpIdTokenAuthLogin(cmd *cobra.Command, infisicalClient infisicalSdk.InfisicalClientInterface) (credential infisicalSdk.MachineIdentityCredential, e error) {
|
||||
@ -205,6 +204,7 @@ var loginCmd = &cobra.Command{
|
||||
if !overrideDomain {
|
||||
domainQuery = false
|
||||
config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL_MANUAL_OVERRIDE)
|
||||
config.INFISICAL_LOGIN_URL = fmt.Sprintf("%s/login", strings.TrimSuffix(config.INFISICAL_URL, "/api"))
|
||||
}
|
||||
|
||||
}
|
||||
@ -226,9 +226,9 @@ var loginCmd = &cobra.Command{
|
||||
|
||||
//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 {
|
||||
fmt.Printf("Login via browser failed. %s", err.Error())
|
||||
//default to cli login on error
|
||||
cliDefaultLogin(&userCredentialsToBeStored)
|
||||
}
|
||||
@ -713,10 +713,62 @@ func askForMFACode() string {
|
||||
return mfaVerifyCode
|
||||
}
|
||||
|
||||
func askToPasteJwtToken(success chan models.UserCredentials, failure chan error) {
|
||||
time.Sleep(time.Second * 5)
|
||||
fmt.Println("\n\nOnce login is completed via browser, the CLI should be authenticated automatically.")
|
||||
fmt.Println("However, if browser fails to communicate with the CLI, please paste the token from the browser below.")
|
||||
|
||||
fmt.Print("\n\nToken: ")
|
||||
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
failure <- err
|
||||
fmt.Println("\nError reading input:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
infisicalPastedToken := strings.TrimSpace(string(bytePassword))
|
||||
|
||||
userCredentials, err := decodePastedBase64Token(infisicalPastedToken)
|
||||
if err != nil {
|
||||
failure <- err
|
||||
fmt.Println("Invalid user credentials provided", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// verify JTW
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(userCredentials.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
isAuthenticated := api.CallIsAuthenticated(httpClient)
|
||||
if !isAuthenticated {
|
||||
fmt.Println("Invalid user credentials provided", err)
|
||||
failure <- err
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
success <- *userCredentials
|
||||
}
|
||||
|
||||
func decodePastedBase64Token(token string) (*models.UserCredentials, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var loginResponse models.UserCredentials
|
||||
|
||||
err = json.Unmarshal(data, &loginResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &loginResponse, nil
|
||||
}
|
||||
|
||||
// Manages the browser login flow.
|
||||
// Returns a UserCredentials object on success and an error on failure
|
||||
func browserCliLogin() (models.UserCredentials, error) {
|
||||
SERVER_TIMEOUT := 60 * 10
|
||||
SERVER_TIMEOUT := 10 * 60
|
||||
|
||||
//create listener
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
@ -728,17 +780,12 @@ func browserCliLogin() (models.UserCredentials, error) {
|
||||
callbackPort := listener.Addr().(*net.TCPAddr).Port
|
||||
url := fmt.Sprintf("%s?callback_port=%d", config.INFISICAL_LOGIN_URL, callbackPort)
|
||||
|
||||
//open browser and login
|
||||
err = browser.OpenURL(url)
|
||||
if err != nil {
|
||||
return models.UserCredentials{}, err
|
||||
}
|
||||
fmt.Printf("\n\nTo complete your login, open this address in your browser: %v \n", url)
|
||||
|
||||
//flow channels
|
||||
success := make(chan models.UserCredentials)
|
||||
failure := make(chan error)
|
||||
timeout := time.After(time.Second * time.Duration(SERVER_TIMEOUT))
|
||||
quit := make(chan bool)
|
||||
|
||||
//terminal state
|
||||
oldState, err := term.GetState(int(os.Stdin.Fd()))
|
||||
@ -761,23 +808,22 @@ func browserCliLogin() (models.UserCredentials, error) {
|
||||
log.Debug().Msgf("Callback server listening on port %d", callbackPort)
|
||||
|
||||
go http.Serve(listener, corsHandler)
|
||||
go askToPasteJwtToken(success, failure)
|
||||
|
||||
for {
|
||||
select {
|
||||
case loginResponse := <-success:
|
||||
_ = closeListener(&listener)
|
||||
fmt.Println("Browser login successful")
|
||||
return loginResponse, nil
|
||||
|
||||
case <-failure:
|
||||
err = closeListener(&listener)
|
||||
return models.UserCredentials{}, err
|
||||
case err := <-failure:
|
||||
serverErr := closeListener(&listener)
|
||||
return models.UserCredentials{}, errors.Join(err, serverErr)
|
||||
|
||||
case <-timeout:
|
||||
_ = closeListener(&listener)
|
||||
return models.UserCredentials{}, errors.New("server timeout")
|
||||
|
||||
case <-quit:
|
||||
return models.UserCredentials{}, errors.New("quitting browser login, defaulting to cli...")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,12 +187,34 @@ var secretsSetCmd = &cobra.Command{
|
||||
|
||||
var secretOperations []models.SecretSetOperation
|
||||
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||
if projectId == "" {
|
||||
util.PrintErrorMessageAndExit("When using service tokens or machine identities, you must set the --projectId flag")
|
||||
}
|
||||
|
||||
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, token)
|
||||
} else {
|
||||
util.RequireLogin()
|
||||
util.RequireLocalWorkspaceFile()
|
||||
if projectId == "" {
|
||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
util.HandleError(err, "unable to get your local config details [err=%v]")
|
||||
}
|
||||
|
||||
secretOperations, err = util.SetEncryptedSecrets(args, secretType, environmentName, secretsPath)
|
||||
projectId = workspaceFile.WorkspaceId
|
||||
}
|
||||
|
||||
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
||||
if err != nil {
|
||||
util.HandleError(err, "unable to authenticate [err=%v]")
|
||||
}
|
||||
|
||||
if loggedInUserDetails.LoginExpired {
|
||||
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||
}
|
||||
|
||||
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, &models.TokenDetails{
|
||||
Type: "",
|
||||
Token: loggedInUserDetails.UserCredentials.JTWToken,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -285,7 +307,7 @@ var secretsDeleteCmd = &cobra.Command{
|
||||
SecretPath: secretsPath,
|
||||
}
|
||||
|
||||
err = api.CallDeleteSecretsV3(httpClient, request)
|
||||
err = api.CallDeleteSecretsRawV3(httpClient, request)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to complete your delete request")
|
||||
}
|
||||
|
@ -140,3 +140,10 @@ type SecretSetOperation struct {
|
||||
SecretValue string
|
||||
SecretOperation string
|
||||
}
|
||||
|
||||
type BackupSecretKeyRing struct {
|
||||
ProjectID string `json:"projectId"`
|
||||
Environment string `json:"environment"`
|
||||
SecretPath string `json:"secretPath"`
|
||||
Secrets []SingleEnvironmentVariable
|
||||
}
|
||||
|
@ -244,10 +244,5 @@ func WriteConfigFile(configFile *models.ConfigFile) error {
|
||||
return fmt.Errorf("writeConfigFile: Unable to write to file [err=%s]", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("writeConfigFile: unable to write config file because an error occurred when write the config to file [err=%s]", err)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ const (
|
||||
|
||||
SERVICE_TOKEN_IDENTIFIER = "service-token"
|
||||
UNIVERSAL_AUTH_TOKEN_IDENTIFIER = "universal-auth-token"
|
||||
|
||||
INFISICAL_BACKUP_SECRET = "infisical-backup-secrets"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -52,10 +52,6 @@ func GetUserCredsFromKeyRing(userEmail string) (credentials models.UserCredentia
|
||||
return models.UserCredentials{}, fmt.Errorf("getUserCredsFromKeyRing: Something went wrong when unmarshalling user creds [err=%s]", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return models.UserCredentials{}, fmt.Errorf("GetUserCredsFromKeyRing: Unable to store user credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
return userCredentials, err
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@ -9,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@ -17,12 +17,13 @@ import (
|
||||
"github.com/Infisical/infisical-merge/packages/models"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/zalando/go-keyring"
|
||||
)
|
||||
|
||||
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool) ([]models.SingleEnvironmentVariable, api.GetServiceTokenDetailsResponse, error) {
|
||||
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool) ([]models.SingleEnvironmentVariable, error) {
|
||||
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
|
||||
if len(serviceTokenParts) < 4 {
|
||||
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
|
||||
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
|
||||
}
|
||||
|
||||
serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2])
|
||||
@ -34,19 +35,19 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str
|
||||
|
||||
serviceTokenDetails, err := api.CallGetServiceTokenDetailsV2(httpClient)
|
||||
if err != nil {
|
||||
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to get service token details. [err=%v]", err)
|
||||
return nil, fmt.Errorf("unable to get service token details. [err=%v]", err)
|
||||
}
|
||||
|
||||
// if multiple scopes are there then user needs to specify which environment and secret path
|
||||
if environment == "" {
|
||||
if len(serviceTokenDetails.Scopes) != 1 {
|
||||
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("you need to provide the --env for multiple environment scoped token")
|
||||
return nil, fmt.Errorf("you need to provide the --env for multiple environment scoped token")
|
||||
} else {
|
||||
environment = serviceTokenDetails.Scopes[0].Environment
|
||||
}
|
||||
}
|
||||
|
||||
encryptedSecrets, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{
|
||||
rawSecrets, err := api.CallGetRawSecretsV3(httpClient, api.GetRawSecretsV3Request{
|
||||
WorkspaceId: serviceTokenDetails.Workspace,
|
||||
Environment: environment,
|
||||
SecretPath: secretPath,
|
||||
@ -54,109 +55,28 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str
|
||||
Recursive: recursive,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, api.GetServiceTokenDetailsResponse{}, err
|
||||
}
|
||||
|
||||
decodedSymmetricEncryptionDetails, err := GetBase64DecodedSymmetricEncryptionDetails(serviceTokenParts[3], serviceTokenDetails.EncryptedKey, serviceTokenDetails.Iv, serviceTokenDetails.Tag)
|
||||
if err != nil {
|
||||
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decode symmetric encryption details [err=%v]", err)
|
||||
}
|
||||
|
||||
plainTextWorkspaceKey, err := crypto.DecryptSymmetric([]byte(serviceTokenParts[3]), decodedSymmetricEncryptionDetails.Cipher, decodedSymmetricEncryptionDetails.Tag, decodedSymmetricEncryptionDetails.IV)
|
||||
if err != nil {
|
||||
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decrypt the required workspace key")
|
||||
}
|
||||
|
||||
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets.Secrets)
|
||||
if err != nil {
|
||||
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
|
||||
}
|
||||
|
||||
if includeImports {
|
||||
plainTextSecrets, err = InjectImportedSecret(plainTextWorkspaceKey, plainTextSecrets, encryptedSecrets.ImportedSecrets)
|
||||
if err != nil {
|
||||
return nil, api.GetServiceTokenDetailsResponse{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return plainTextSecrets, serviceTokenDetails, nil
|
||||
}
|
||||
|
||||
func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, workspaceId string, environmentName string, tagSlugs string, secretsPath string, includeImports bool, recursive bool) ([]models.SingleEnvironmentVariable, error) {
|
||||
httpClient := resty.New()
|
||||
httpClient.SetAuthToken(JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
request := api.GetEncryptedWorkspaceKeyRequest{
|
||||
WorkspaceId: workspaceId,
|
||||
}
|
||||
|
||||
workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get your encrypted workspace key. [err=%v]", err)
|
||||
}
|
||||
|
||||
encryptedWorkspaceKey, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
|
||||
if err != nil {
|
||||
HandleError(err, "Unable to get bytes represented by the base64 for encryptedWorkspaceKey")
|
||||
}
|
||||
|
||||
encryptedWorkspaceKeySenderPublicKey, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey)
|
||||
if err != nil {
|
||||
HandleError(err, "Unable to get bytes represented by the base64 for encryptedWorkspaceKeySenderPublicKey")
|
||||
}
|
||||
|
||||
encryptedWorkspaceKeyNonce, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
|
||||
if err != nil {
|
||||
HandleError(err, "Unable to get bytes represented by the base64 for encryptedWorkspaceKeyNonce")
|
||||
}
|
||||
|
||||
currentUsersPrivateKey, err := base64.StdEncoding.DecodeString(receiversPrivateKey)
|
||||
if err != nil {
|
||||
HandleError(err, "Unable to get bytes represented by the base64 for currentUsersPrivateKey")
|
||||
}
|
||||
|
||||
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
|
||||
log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
|
||||
PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
|
||||
}
|
||||
|
||||
plainTextWorkspaceKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
||||
|
||||
getSecretsRequest := api.GetEncryptedSecretsV3Request{
|
||||
WorkspaceId: workspaceId,
|
||||
Environment: environmentName,
|
||||
IncludeImport: includeImports,
|
||||
Recursive: recursive,
|
||||
// TagSlugs: tagSlugs,
|
||||
}
|
||||
|
||||
if secretsPath != "" {
|
||||
getSecretsRequest.SecretPath = secretsPath
|
||||
}
|
||||
|
||||
encryptedSecrets, err := api.CallGetSecretsV3(httpClient, getSecretsRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plainTextSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, encryptedSecrets.Secrets)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
|
||||
plainTextSecrets := []models.SingleEnvironmentVariable{}
|
||||
|
||||
for _, secret := range rawSecrets.Secrets {
|
||||
plainTextSecrets = append(plainTextSecrets, models.SingleEnvironmentVariable{Key: secret.SecretKey, Value: secret.SecretValue, Type: secret.Type, WorkspaceId: secret.Workspace})
|
||||
}
|
||||
|
||||
if includeImports {
|
||||
plainTextSecrets, err = InjectImportedSecret(plainTextWorkspaceKey, plainTextSecrets, encryptedSecrets.ImportedSecrets)
|
||||
plainTextSecrets, err = InjectRawImportedSecret(plainTextSecrets, rawSecrets.Imports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return plainTextSecrets, nil
|
||||
|
||||
}
|
||||
|
||||
func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool) (models.PlaintextSecretResult, error) {
|
||||
func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool) (models.PlaintextSecretResult, error) {
|
||||
httpClient := resty.New()
|
||||
httpClient.SetAuthToken(accessToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
@ -180,9 +100,6 @@ func GetPlainTextSecretsViaMachineIdentity(accessToken string, workspaceId strin
|
||||
}
|
||||
|
||||
plainTextSecrets := []models.SingleEnvironmentVariable{}
|
||||
if err != nil {
|
||||
return models.PlaintextSecretResult{}, fmt.Errorf("unable to decrypt your secrets [err=%v]", err)
|
||||
}
|
||||
|
||||
for _, secret := range rawSecrets.Secrets {
|
||||
plainTextSecrets = append(plainTextSecrets, models.SingleEnvironmentVariable{Key: secret.SecretKey, Value: secret.SecretValue, Type: secret.Type, WorkspaceId: secret.Workspace})
|
||||
@ -226,34 +143,6 @@ func CreateDynamicSecretLease(accessToken string, projectSlug string, environmen
|
||||
}, nil
|
||||
}
|
||||
|
||||
func InjectImportedSecret(plainTextWorkspaceKey []byte, secrets []models.SingleEnvironmentVariable, importedSecrets []api.ImportedSecretV3) ([]models.SingleEnvironmentVariable, error) {
|
||||
if importedSecrets == nil {
|
||||
return secrets, nil
|
||||
}
|
||||
|
||||
hasOverriden := make(map[string]bool)
|
||||
for _, sec := range secrets {
|
||||
hasOverriden[sec.Key] = true
|
||||
}
|
||||
|
||||
for i := len(importedSecrets) - 1; i >= 0; i-- {
|
||||
importSec := importedSecrets[i]
|
||||
plainTextImportedSecrets, err := GetPlainTextSecrets(plainTextWorkspaceKey, importSec.Secrets)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decrypt your imported secrets [err=%v]", err)
|
||||
}
|
||||
|
||||
for _, sec := range plainTextImportedSecrets {
|
||||
if _, ok := hasOverriden[sec.Key]; !ok {
|
||||
secrets = append(secrets, sec)
|
||||
hasOverriden[sec.Key] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return secrets, nil
|
||||
}
|
||||
|
||||
func InjectRawImportedSecret(secrets []models.SingleEnvironmentVariable, importedSecrets []api.ImportedRawSecretV3) ([]models.SingleEnvironmentVariable, error) {
|
||||
if importedSecrets == nil {
|
||||
return secrets, nil
|
||||
@ -361,18 +250,19 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
||||
infisicalDotJson.WorkspaceId = params.WorkspaceId
|
||||
}
|
||||
|
||||
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, infisicalDotJson.WorkspaceId,
|
||||
params.Environment, params.TagSlugs, params.SecretsPath, params.IncludeImport, params.Recursive)
|
||||
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
|
||||
res, err := GetPlainTextSecretsV3(loggedInUserDetails.UserCredentials.JTWToken, infisicalDotJson.WorkspaceId,
|
||||
params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
|
||||
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", err)
|
||||
|
||||
backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32]
|
||||
if errorToReturn == nil {
|
||||
WriteBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath, backupSecretsEncryptionKey, secretsToReturn)
|
||||
if err == nil {
|
||||
WriteBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath, res.Secrets)
|
||||
}
|
||||
|
||||
secretsToReturn = res.Secrets
|
||||
errorToReturn = err
|
||||
// only attempt to serve cached secrets if no internet connection and if at least one secret cached
|
||||
if !isConnected {
|
||||
backedSecrets, err := ReadBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath, backupSecretsEncryptionKey)
|
||||
backedSecrets, err := ReadBackupSecrets(infisicalDotJson.WorkspaceId, params.Environment, params.SecretsPath)
|
||||
if len(backedSecrets) > 0 {
|
||||
PrintWarning("Unable to fetch latest secret(s) due to connection error, serving secrets from last successful fetch. For more info, run with --debug")
|
||||
secretsToReturn = backedSecrets
|
||||
@ -383,7 +273,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
||||
} else {
|
||||
if params.InfisicalToken != "" {
|
||||
log.Debug().Msg("Trying to fetch secrets using service token")
|
||||
secretsToReturn, _, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
|
||||
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
|
||||
} else if params.UniversalAuthAccessToken != "" {
|
||||
|
||||
if params.WorkspaceId == "" {
|
||||
@ -391,7 +281,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
||||
}
|
||||
|
||||
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)
|
||||
res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
|
||||
|
||||
errorToReturn = err
|
||||
secretsToReturn = res.Secrets
|
||||
@ -556,174 +446,71 @@ func OverrideSecrets(secrets []models.SingleEnvironmentVariable, secretType stri
|
||||
return secretsToReturn
|
||||
}
|
||||
|
||||
func GetPlainTextSecrets(key []byte, encryptedSecrets []api.EncryptedSecretV3) ([]models.SingleEnvironmentVariable, error) {
|
||||
plainTextSecrets := []models.SingleEnvironmentVariable{}
|
||||
for _, secret := range encryptedSecrets {
|
||||
// Decrypt key
|
||||
key_iv, err := base64.StdEncoding.DecodeString(secret.SecretKeyIV)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode secret IV for secret key")
|
||||
}
|
||||
|
||||
key_tag, err := base64.StdEncoding.DecodeString(secret.SecretKeyTag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode secret authentication tag for secret key")
|
||||
}
|
||||
|
||||
key_ciphertext, err := base64.StdEncoding.DecodeString(secret.SecretKeyCiphertext)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
|
||||
}
|
||||
|
||||
plainTextKey, err := crypto.DecryptSymmetric(key, key_ciphertext, key_tag, key_iv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to symmetrically decrypt secret key")
|
||||
}
|
||||
|
||||
// Decrypt value
|
||||
value_iv, err := base64.StdEncoding.DecodeString(secret.SecretValueIV)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode secret IV for secret value")
|
||||
}
|
||||
|
||||
value_tag, err := base64.StdEncoding.DecodeString(secret.SecretValueTag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode secret authentication tag for secret value")
|
||||
}
|
||||
|
||||
value_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretValueCiphertext)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
|
||||
}
|
||||
|
||||
plainTextValue, err := crypto.DecryptSymmetric(key, value_ciphertext, value_tag, value_iv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to symmetrically decrypt secret value")
|
||||
}
|
||||
|
||||
// Decrypt comment
|
||||
comment_iv, err := base64.StdEncoding.DecodeString(secret.SecretCommentIV)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode secret IV for secret value")
|
||||
}
|
||||
|
||||
comment_tag, err := base64.StdEncoding.DecodeString(secret.SecretCommentTag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode secret authentication tag for secret value")
|
||||
}
|
||||
|
||||
comment_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretCommentCiphertext)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
|
||||
}
|
||||
|
||||
plainTextComment, err := crypto.DecryptSymmetric(key, comment_ciphertext, comment_tag, comment_iv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to symmetrically decrypt secret comment")
|
||||
}
|
||||
|
||||
plainTextSecret := models.SingleEnvironmentVariable{
|
||||
Key: string(plainTextKey),
|
||||
Value: string(plainTextValue),
|
||||
Type: string(secret.Type),
|
||||
ID: secret.ID,
|
||||
Tags: secret.Tags,
|
||||
Comment: string(plainTextComment),
|
||||
}
|
||||
|
||||
plainTextSecrets = append(plainTextSecrets, plainTextSecret)
|
||||
}
|
||||
|
||||
return plainTextSecrets, nil
|
||||
}
|
||||
|
||||
func WriteBackupSecrets(workspace string, environment string, secretsPath string, encryptionKey []byte, secrets []models.SingleEnvironmentVariable) error {
|
||||
formattedPath := strings.ReplaceAll(secretsPath, "/", "-")
|
||||
fileName := fmt.Sprintf("secrets_%s_%s_%s", workspace, environment, formattedPath)
|
||||
secrets_backup_folder_name := "secrets-backup"
|
||||
|
||||
_, fullConfigFileDirPath, err := GetFullConfigFilePath()
|
||||
func WriteBackupSecrets(workspace string, environment string, secretsPath string, secrets []models.SingleEnvironmentVariable) error {
|
||||
var backedUpSecrets []models.BackupSecretKeyRing
|
||||
secretValueInKeyRing, err := GetValueInKeyring(INFISICAL_BACKUP_SECRET)
|
||||
if err != nil {
|
||||
return fmt.Errorf("WriteBackupSecrets: unable to get full config folder path [err=%s]", err)
|
||||
}
|
||||
|
||||
// create secrets backup directory
|
||||
fullPathToSecretsBackupFolder := fmt.Sprintf("%s/%s", fullConfigFileDirPath, secrets_backup_folder_name)
|
||||
if _, err := os.Stat(fullPathToSecretsBackupFolder); errors.Is(err, os.ErrNotExist) {
|
||||
err := os.Mkdir(fullPathToSecretsBackupFolder, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
if err == keyring.ErrUnsupportedPlatform {
|
||||
return errors.New("your OS does not support keyring. Consider using a service token https://infisical.com/docs/documentation/platform/token")
|
||||
} else if err != keyring.ErrNotFound {
|
||||
return fmt.Errorf("something went wrong, failed to retrieve value from system keyring [error=%v]", err)
|
||||
}
|
||||
}
|
||||
_ = json.Unmarshal([]byte(secretValueInKeyRing), &backedUpSecrets)
|
||||
|
||||
var encryptedSecrets []models.SymmetricEncryptionResult
|
||||
for _, secret := range secrets {
|
||||
marshaledSecrets, _ := json.Marshal(secret)
|
||||
result, err := crypto.EncryptSymmetric(marshaledSecrets, encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedSecrets = append(encryptedSecrets, result)
|
||||
backedUpSecrets = slices.DeleteFunc(backedUpSecrets, func(e models.BackupSecretKeyRing) bool {
|
||||
return e.SecretPath == secretsPath && e.ProjectID == workspace && e.Environment == environment
|
||||
})
|
||||
newBackupSecret := models.BackupSecretKeyRing{
|
||||
ProjectID: workspace,
|
||||
Environment: environment,
|
||||
SecretPath: secretsPath,
|
||||
Secrets: secrets,
|
||||
}
|
||||
backedUpSecrets = append(backedUpSecrets, newBackupSecret)
|
||||
|
||||
listOfSecretsMarshalled, _ := json.Marshal(encryptedSecrets)
|
||||
err = os.WriteFile(fmt.Sprintf("%s/%s", fullPathToSecretsBackupFolder, fileName), listOfSecretsMarshalled, 0600)
|
||||
listOfSecretsMarshalled, err := json.Marshal(backedUpSecrets)
|
||||
if err != nil {
|
||||
return fmt.Errorf("WriteBackupSecrets: Unable to write backup secrets to file [err=%s]", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = SetValueInKeyring(INFISICAL_BACKUP_SECRET, string(listOfSecretsMarshalled))
|
||||
if err != nil {
|
||||
return fmt.Errorf("StoreUserCredsInKeyRing: unable to store user credentials because [err=%s]", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadBackupSecrets(workspace string, environment string, secretsPath string, encryptionKey []byte) ([]models.SingleEnvironmentVariable, error) {
|
||||
formattedPath := strings.ReplaceAll(secretsPath, "/", "-")
|
||||
fileName := fmt.Sprintf("secrets_%s_%s_%s", workspace, environment, formattedPath)
|
||||
secrets_backup_folder_name := "secrets-backup"
|
||||
|
||||
_, fullConfigFileDirPath, err := GetFullConfigFilePath()
|
||||
func ReadBackupSecrets(workspace string, environment string, secretsPath string) ([]models.SingleEnvironmentVariable, error) {
|
||||
secretValueInKeyRing, err := GetValueInKeyring(INFISICAL_BACKUP_SECRET)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ReadBackupSecrets: unable to write config file because an error occurred when getting config file path [err=%s]", err)
|
||||
if err == keyring.ErrUnsupportedPlatform {
|
||||
return nil, errors.New("your OS does not support keyring. Consider using a service token https://infisical.com/docs/documentation/platform/token")
|
||||
} else if err == keyring.ErrNotFound {
|
||||
return nil, errors.New("credentials not found in system keyring")
|
||||
} else {
|
||||
return nil, fmt.Errorf("something went wrong, failed to retrieve value from system keyring [error=%v]", err)
|
||||
}
|
||||
}
|
||||
|
||||
fullPathToSecretsBackupFolder := fmt.Sprintf("%s/%s", fullConfigFileDirPath, secrets_backup_folder_name)
|
||||
if _, err := os.Stat(fullPathToSecretsBackupFolder); errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
encryptedBackupSecretsFilePath := fmt.Sprintf("%s/%s", fullPathToSecretsBackupFolder, fileName)
|
||||
|
||||
encryptedBackupSecretsAsBytes, err := os.ReadFile(encryptedBackupSecretsFilePath)
|
||||
var backedUpSecrets []models.BackupSecretKeyRing
|
||||
err = json.Unmarshal([]byte(secretValueInKeyRing), &backedUpSecrets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("getUserCredsFromKeyRing: Something went wrong when unmarshalling user creds [err=%s]", err)
|
||||
}
|
||||
|
||||
var listOfEncryptedBackupSecrets []models.SymmetricEncryptionResult
|
||||
|
||||
_ = json.Unmarshal(encryptedBackupSecretsAsBytes, &listOfEncryptedBackupSecrets)
|
||||
|
||||
var plainTextSecrets []models.SingleEnvironmentVariable
|
||||
for _, encryptedSecret := range listOfEncryptedBackupSecrets {
|
||||
result, err := crypto.DecryptSymmetric(encryptionKey, encryptedSecret.CipherText, encryptedSecret.AuthTag, encryptedSecret.Nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for _, backupSecret := range backedUpSecrets {
|
||||
if backupSecret.Environment == environment && backupSecret.ProjectID == workspace && backupSecret.SecretPath == secretsPath {
|
||||
return backupSecret.Secrets, nil
|
||||
}
|
||||
|
||||
var plainTextSecret models.SingleEnvironmentVariable
|
||||
|
||||
err = json.Unmarshal(result, &plainTextSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plainTextSecrets = append(plainTextSecrets, plainTextSecret)
|
||||
}
|
||||
|
||||
return plainTextSecrets, nil
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func DeleteBackupSecrets() error {
|
||||
// keeping this logic for now. Need to remove it later as more users migrate keyring would be used and this folder will be removed completely by then
|
||||
secrets_backup_folder_name := "secrets-backup"
|
||||
|
||||
_, fullConfigFileDirPath, err := GetFullConfigFilePath()
|
||||
@ -733,6 +520,8 @@ func DeleteBackupSecrets() error {
|
||||
|
||||
fullPathToSecretsBackupFolder := fmt.Sprintf("%s/%s", fullConfigFileDirPath, secrets_backup_folder_name)
|
||||
|
||||
DeleteValueInKeyring(INFISICAL_BACKUP_SECRET)
|
||||
|
||||
return os.RemoveAll(fullPathToSecretsBackupFolder)
|
||||
}
|
||||
|
||||
@ -809,200 +598,6 @@ func GetPlainTextWorkspaceKey(authenticationToken string, receiverPrivateKey str
|
||||
return crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey), nil
|
||||
}
|
||||
|
||||
func SetEncryptedSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string) ([]models.SecretSetOperation, error) {
|
||||
|
||||
workspaceFile, err := GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get your local config details [err=%v]", err)
|
||||
}
|
||||
|
||||
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to authenticate [err=%v]", err)
|
||||
}
|
||||
|
||||
if loggedInUserDetails.LoginExpired {
|
||||
PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||
}
|
||||
|
||||
httpClient := resty.New().
|
||||
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
request := api.GetEncryptedWorkspaceKeyRequest{
|
||||
WorkspaceId: workspaceFile.WorkspaceId,
|
||||
}
|
||||
|
||||
workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get your encrypted workspace key [err=%v]", err)
|
||||
}
|
||||
|
||||
encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
|
||||
encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey)
|
||||
encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
|
||||
currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey)
|
||||
|
||||
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
|
||||
log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
|
||||
PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
|
||||
}
|
||||
|
||||
// decrypt workspace key
|
||||
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
||||
|
||||
infisicalTokenEnv := os.Getenv(INFISICAL_TOKEN_NAME)
|
||||
|
||||
// pull current secrets
|
||||
secrets, err := GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath, InfisicalToken: infisicalTokenEnv}, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to retrieve secrets [err=%v]", err)
|
||||
}
|
||||
|
||||
secretsToCreate := []api.Secret{}
|
||||
secretsToModify := []api.Secret{}
|
||||
secretOperations := []models.SecretSetOperation{}
|
||||
|
||||
sharedSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
|
||||
personalSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
|
||||
|
||||
for _, secret := range secrets {
|
||||
if secret.Type == SECRET_TYPE_PERSONAL {
|
||||
personalSecretMapByName[secret.Key] = secret
|
||||
} else {
|
||||
sharedSecretMapByName[secret.Key] = secret
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range secretArgs {
|
||||
splitKeyValueFromArg := strings.SplitN(arg, "=", 2)
|
||||
if splitKeyValueFromArg[0] == "" || splitKeyValueFromArg[1] == "" {
|
||||
PrintErrorMessageAndExit("ensure that each secret has a none empty key and value. Modify the input and try again")
|
||||
}
|
||||
|
||||
if unicode.IsNumber(rune(splitKeyValueFromArg[0][0])) {
|
||||
PrintErrorMessageAndExit("keys of secrets cannot start with a number. Modify the key name(s) and try again")
|
||||
}
|
||||
|
||||
// Key and value from argument
|
||||
key := strings.TrimSpace(splitKeyValueFromArg[0])
|
||||
value := splitKeyValueFromArg[1]
|
||||
|
||||
hashedKey := fmt.Sprintf("%x", sha256.Sum256([]byte(key)))
|
||||
encryptedKey, err := crypto.EncryptSymmetric([]byte(key), []byte(plainTextEncryptionKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to encrypt your secrets [err=%v]", err)
|
||||
}
|
||||
|
||||
hashedValue := fmt.Sprintf("%x", sha256.Sum256([]byte(value)))
|
||||
encryptedValue, err := crypto.EncryptSymmetric([]byte(value), []byte(plainTextEncryptionKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to encrypt your secrets [err=%v]", err)
|
||||
}
|
||||
|
||||
var existingSecret models.SingleEnvironmentVariable
|
||||
var doesSecretExist bool
|
||||
|
||||
if secretType == SECRET_TYPE_SHARED {
|
||||
existingSecret, doesSecretExist = sharedSecretMapByName[key]
|
||||
} else {
|
||||
existingSecret, doesSecretExist = personalSecretMapByName[key]
|
||||
}
|
||||
|
||||
if doesSecretExist {
|
||||
// case: secret exists in project so it needs to be modified
|
||||
encryptedSecretDetails := api.Secret{
|
||||
ID: existingSecret.ID,
|
||||
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
|
||||
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
|
||||
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
|
||||
SecretValueHash: hashedValue,
|
||||
PlainTextKey: key,
|
||||
Type: existingSecret.Type,
|
||||
}
|
||||
|
||||
// Only add to modifications if the value is different
|
||||
if existingSecret.Value != value {
|
||||
secretsToModify = append(secretsToModify, encryptedSecretDetails)
|
||||
secretOperations = append(secretOperations, models.SecretSetOperation{
|
||||
SecretKey: key,
|
||||
SecretValue: value,
|
||||
SecretOperation: "SECRET VALUE MODIFIED",
|
||||
})
|
||||
} else {
|
||||
// Current value is same as exisitng so no change
|
||||
secretOperations = append(secretOperations, models.SecretSetOperation{
|
||||
SecretKey: key,
|
||||
SecretValue: value,
|
||||
SecretOperation: "SECRET VALUE UNCHANGED",
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
// case: secret doesn't exist in project so it needs to be created
|
||||
encryptedSecretDetails := api.Secret{
|
||||
SecretKeyCiphertext: base64.StdEncoding.EncodeToString(encryptedKey.CipherText),
|
||||
SecretKeyIV: base64.StdEncoding.EncodeToString(encryptedKey.Nonce),
|
||||
SecretKeyTag: base64.StdEncoding.EncodeToString(encryptedKey.AuthTag),
|
||||
SecretKeyHash: hashedKey,
|
||||
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
|
||||
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
|
||||
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
|
||||
SecretValueHash: hashedValue,
|
||||
Type: secretType,
|
||||
PlainTextKey: key,
|
||||
}
|
||||
secretsToCreate = append(secretsToCreate, encryptedSecretDetails)
|
||||
secretOperations = append(secretOperations, models.SecretSetOperation{
|
||||
SecretKey: key,
|
||||
SecretValue: value,
|
||||
SecretOperation: "SECRET CREATED",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, secret := range secretsToCreate {
|
||||
createSecretRequest := api.CreateSecretV3Request{
|
||||
WorkspaceID: workspaceFile.WorkspaceId,
|
||||
Environment: environmentName,
|
||||
SecretName: secret.PlainTextKey,
|
||||
SecretKeyCiphertext: secret.SecretKeyCiphertext,
|
||||
SecretKeyIV: secret.SecretKeyIV,
|
||||
SecretKeyTag: secret.SecretKeyTag,
|
||||
SecretValueCiphertext: secret.SecretValueCiphertext,
|
||||
SecretValueIV: secret.SecretValueIV,
|
||||
SecretValueTag: secret.SecretValueTag,
|
||||
Type: secret.Type,
|
||||
SecretPath: secretsPath,
|
||||
}
|
||||
|
||||
err = api.CallCreateSecretsV3(httpClient, createSecretRequest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process new secret creations [err=%v]", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, secret := range secretsToModify {
|
||||
updateSecretRequest := api.UpdateSecretByNameV3Request{
|
||||
WorkspaceID: workspaceFile.WorkspaceId,
|
||||
Environment: environmentName,
|
||||
SecretValueCiphertext: secret.SecretValueCiphertext,
|
||||
SecretValueIV: secret.SecretValueIV,
|
||||
SecretValueTag: secret.SecretValueTag,
|
||||
Type: secret.Type,
|
||||
SecretPath: secretsPath,
|
||||
}
|
||||
|
||||
err = api.CallUpdateSecretsV3(httpClient, updateSecretRequest, secret.PlainTextKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to process secret update request [err=%v]", err)
|
||||
}
|
||||
}
|
||||
|
||||
return secretOperations, nil
|
||||
|
||||
}
|
||||
|
||||
func SetRawSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string, projectId string, tokenDetails *models.TokenDetails) ([]models.SecretSetOperation, error) {
|
||||
|
||||
if tokenDetails == nil {
|
||||
@ -1012,7 +607,9 @@ func SetRawSecrets(secretArgs []string, secretType string, environmentName strin
|
||||
getAllEnvironmentVariablesRequest := models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath, WorkspaceId: projectId}
|
||||
if tokenDetails.Type == UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
|
||||
getAllEnvironmentVariablesRequest.UniversalAuthAccessToken = tokenDetails.Token
|
||||
} else {
|
||||
}
|
||||
|
||||
if tokenDetails.Type == SERVICE_TOKEN_IDENTIFIER {
|
||||
getAllEnvironmentVariablesRequest.InfisicalToken = tokenDetails.Token
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "List"
|
||||
openapi: "GET /api/v2/workspace/{slug}/cas"
|
||||
---
|
4
docs/api-reference/endpoints/certificates/list.mdx
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "List"
|
||||
openapi: "GET /api/v2/workspace/{slug}/certificates"
|
||||
---
|
@ -4,6 +4,30 @@ title: "Changelog"
|
||||
|
||||
The changelog below reflects new product developments and updates on a monthly basis.
|
||||
|
||||
## July 2024
|
||||
- Released the official [Ruby SDK](https://infisical.com/docs/sdks/languages/ruby).
|
||||
- Increased the speed and efficiency of secret operations.
|
||||
- Released AWS KMS wrapping (bring your own key).
|
||||
- Users can now log in to CLI via SSO in non-browser environments.
|
||||
- Released [Slack Webhooks](https://infisical.com/docs/documentation/platform/webhooks).
|
||||
- Added [Dynamic Secrets with MS SQL](https://infisical.com/docs/documentation/platform/dynamic-secrets/mssql).
|
||||
- Redesigned and simplified the Machine Identities page.
|
||||
- Added the ability to move secrets/folders to another location.
|
||||
- Added [OIDC](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general) support to CLI, Go SDK, and more.
|
||||
- Released [Linux installer for Infisical](https://infisical.com/docs/self-hosting/deployment-options/native/standalone-binary).
|
||||
|
||||
## June 2024
|
||||
- Released [Infisical PKI](https://infisical.com/docs/documentation/platform/pki/overview).
|
||||
- Released the official [Go SDK](https://infisical.com/docs/sdks/languages/go).
|
||||
- Released [OIDC Authentication method](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general).
|
||||
- Allowed users to configure log retention periods on self-hosted instances.
|
||||
- Added [tags](https://registry.terraform.io/providers/Infisical/infisical/latest/docs/resources/secret_tag) to terraform provider.
|
||||
- Released [public secret sharing](https://share.infisical.com).
|
||||
- Built a [native integration with Rundeck](https://infisical.com/docs/integrations/cicd/rundeck).
|
||||
- Added list view for projects in the dashboard.
|
||||
- Fixed offline coding mode in CLI.
|
||||
- Users are now able to leave a particular project themselves.
|
||||
|
||||
## May 2024
|
||||
- Released [AWS](https://infisical.com/docs/documentation/platform/identities/aws-auth), [GCP](https://infisical.com/docs/documentation/platform/identities/gcp-auth), [Azure](https://infisical.com/docs/documentation/platform/identities/azure-auth), and [Kubernetes](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth) Native Auth Methods.
|
||||
- Added [Secret Sharing](https://infisical.com/docs/documentation/platform/secret-sharing) functionality for sharing sensitive data through encrypted links – within and outside of an organization.
|
||||
|
23
docs/documentation/guides/migrating-from-envkey.mdx
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "Migrating from EnvKey to Infisical"
|
||||
sidebarTitle: "Migration"
|
||||
description: "Learn how to migrate from EnvKey to Infisical in the easiest way possible."
|
||||
---
|
||||
|
||||
## What is Infisical?
|
||||
|
||||
[Infisical](https://infisical.com) is an open-source all-in-one secret management platform that helps developers manage secrets (e.g., API-keys, DB access tokens, [certificates](https://infisical.com/docs/documentation/platform/pki/overview)) across their infrastructure. In addition, Infisical provides [secret sharing](https://infisical.com/docs/documentation/platform/secret-sharing) functionality, ability to [prevent secret leaks](https://infisical.com/docs/cli/scanning-overview), and more.
|
||||
|
||||
Infisical is used by 10,000+ organizations across all indsutries including First American Financial Corporation, Deivery Hero, and [Hugging Face](https://infisical.com/customers/hugging-face).
|
||||
|
||||
## Migrating from EnvKey
|
||||
|
||||
To facilitate customer transition from EnvKey to Infisical, we have been working closely with the EnvKey team to provide a simple migration path for all EnvKey customers.
|
||||
|
||||
## Automated migration
|
||||
|
||||
Our team is currently working on creating an automated migration process that would include secrets, policies, and other important resources. If you are interested in that, please [reach out to our team](mailto:support@infisical.com) with any questions.
|
||||
|
||||
## Talk to our team
|
||||
|
||||
To make the migration process even more seamless, you can [schedule a meeting with our team](https://infisical.cal.com/vlad/migration-from-envkey-to-infisical) to learn more about how Infisical compares to EnvKey and discuss unique needs of your organization. You are also welcome to email us at [support@infisical.com](mailto:support@infisical.com) to ask any questions or get any technical help.
|
@ -6,7 +6,7 @@ description: "Learn how to request access to sensitive resources in Infisical."
|
||||
In certain situations, developers need to expand their access to a certain new project or a sensitive environment. For those use cases, it is helpful to utilize Infisical's **Access Requests** functionality.
|
||||
|
||||
This functionality works in the following way:
|
||||
1. A project administrator sets up a policy that assigns access managers (also known as eligible approvers) to a certain sensitive folder or environment.
|
||||
1. A project administrator sets up an access policy that assigns access managers (also known as eligible approvers) to a certain sensitive folder or environment.
|
||||

|
||||

|
||||
|
||||
@ -14,9 +14,14 @@ This functionality works in the following way:
|
||||

|
||||

|
||||
|
||||
3. An eligible approver can approve or reject the access request.
|
||||

|
||||
4. An eligible approver can approve or reject the access request.
|
||||
{/*  */}
|
||||

|
||||
|
||||
4. As soon as the request is approved, developer is able to access the sought resources.
|
||||
<Info>
|
||||
If the access request matches with a policy that has a **Soft** enforcement level, the requester may bypass the policy and get access to the resource without full approval.
|
||||
</Info>
|
||||
|
||||
5. As soon as the request is approved, developer is able to access the sought resources.
|
||||

|
||||
|
||||
|
@ -18,16 +18,26 @@ In a similar way, to solve the above-mentioned issues, Infisical provides a feat
|
||||
|
||||
### Setting a policy
|
||||
|
||||
First, you would need to create a set of policies for a certain environment. In the example below, a generic policy for a production environment is shown. In this case, any user who submits a change to `prod` would first have to get an approval by a predefined approver (or multiple approvers).
|
||||
First, you would need to create a set of policies for a certain environment. In the example below, a generic change policy for a production environment is shown. In this case, any user who submits a change to `prod` would first have to get an approval by a predefined approver (or multiple approvers).
|
||||
|
||||

|
||||
|
||||
### Policy enforcement levels
|
||||
|
||||
The enforcement level determines how strict the policy is. A **Hard** enforcement level means that any change that matches the policy will need full approval prior merging. A **Soft** enforcement level allows for break glass functionality on the request. If a change request is bypassed, the approvers will be notified via email.
|
||||
|
||||
### Example of creating a change policy
|
||||
|
||||
When creating a policy, you can choose the type of policy you want to create. In this case, we will be creating a `Change Policy`. Other types of policies include `Access Policy` that creates policies for **[Access Requests](/documentation/platform/access-controls/access-requests)**.
|
||||
|
||||

|
||||
|
||||
### Example of updating secrets with Approval workflows
|
||||
|
||||
When a user submits a change to an enviropnment that is under a particular policy, a corresponsing change request will go to a predefined approver (or multiple approvers).
|
||||
|
||||

|
||||
|
||||
An approver is notified by email and/or Slack as soon as the request is initiated. In the Infisical Dashboard, they will be able to `approve` and `merge` (or `deny`) a request for a change in a particular environment. After that, depending on the workflows setup, the change will be automatically propagated to the right applications (e.g., using [Infisical Kubernetes Operator](https://infisical.com/docs/integrations/platforms/kubernetes)).
|
||||
Approvers are notified by email and/or Slack as soon as the request is initiated. In the Infisical Dashboard, they will be able to `approve` and `merge` (or `deny`) a request for a change in a particular environment. After that, depending on the workflows setup, the change will be automatically propagated to the right applications (e.g., using [Infisical Kubernetes Operator](https://infisical.com/docs/integrations/platforms/kubernetes)).
|
||||
|
||||

|
@ -21,7 +21,8 @@ With its zero-knowledge architecture, secrets shared via Infisical remain unread
|
||||
zero knowledge architecture.
|
||||
</Note>
|
||||
|
||||
3. Click on the **Share Secret** button. Set the secret, its expiration time as well as the number of views allowed. It expires as soon as any of the conditions are met.
|
||||
3. Click on the **Share Secret** button. Set the secret, its expiration time and specify if the secret can be viewed only once. It expires as soon as any of the conditions are met.
|
||||
Also, specify if the secret can be accessed by anyone or only people within your organization.
|
||||
|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 297 KiB |
BIN
docs/images/platform/access-controls/access-request-bypass.png
Normal file
After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 43 KiB |
BIN
docs/images/platform/pr-workflows/create-change-policy.png
Normal file
After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 39 KiB |
BIN
docs/images/self-hosting/deployment-options/native/ha-stack.png
Normal file
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 330 KiB |
@ -15,7 +15,7 @@ Prerequisites:
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Copy your [Account ID](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/) from Account > Workers & Pages > Overview
|
||||
|
||||
@ -35,10 +35,12 @@ Prerequisites:
|
||||
breaks E2EE, it's necessary for Infisical to sync the environment variables to
|
||||
the cloud platform.
|
||||
</Info>
|
||||
|
||||
</Step>
|
||||
<Step title="Start integration">
|
||||
Select which Infisical environment secrets you want to sync to Cloudflare Workers and press create integration to start syncing secrets.
|
||||
|
||||

|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
</Steps>
|
||||
|
@ -217,7 +217,7 @@
|
||||
"pages": [
|
||||
"self-hosting/overview",
|
||||
{
|
||||
"group": "Installation methods",
|
||||
"group": "Containerized installation methods",
|
||||
"pages": [
|
||||
"self-hosting/deployment-options/standalone-infisical",
|
||||
"self-hosting/deployment-options/docker-swarm",
|
||||
@ -406,6 +406,7 @@
|
||||
"sdks/languages/node",
|
||||
"sdks/languages/python",
|
||||
"sdks/languages/go",
|
||||
"sdks/languages/ruby",
|
||||
"sdks/languages/java",
|
||||
"sdks/languages/csharp"
|
||||
]
|
||||
@ -653,6 +654,7 @@
|
||||
{
|
||||
"group": "Certificate Authorities",
|
||||
"pages": [
|
||||
"api-reference/endpoints/certificate-authorities/list",
|
||||
"api-reference/endpoints/certificate-authorities/create",
|
||||
"api-reference/endpoints/certificate-authorities/read",
|
||||
"api-reference/endpoints/certificate-authorities/update",
|
||||
@ -668,6 +670,7 @@
|
||||
{
|
||||
"group": "Certificates",
|
||||
"pages": [
|
||||
"api-reference/endpoints/certificates/list",
|
||||
"api-reference/endpoints/certificates/read",
|
||||
"api-reference/endpoints/certificates/revoke",
|
||||
"api-reference/endpoints/certificates/delete",
|
||||
|
@ -118,6 +118,10 @@ namespace Example
|
||||
Your self-hosted absolute site URL including the protocol (e.g. `https://app.infisical.com`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="SslCertificatePath" optional>
|
||||
Optionally provide a path to a custom SSL certificate file. This can be substituted by setting the `INFISICAL_SSL_CERTIFICATE` environment variable to the contents of the certificate.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="Auth" type="AuthenticationOptions">
|
||||
The authentication object to use for the client. This is required unless you're using environment variables.
|
||||
</ParamField>
|
||||
|
@ -122,6 +122,10 @@ public class App {
|
||||
Your self-hosted absolute site URL including the protocol (e.g. `https://app.infisical.com`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="setSSLCertificatePath()">
|
||||
Optionally provide a path to a custom SSL certificate file. This can be substituted by setting the `INFISICAL_SSL_CERTIFICATE` environment variable to the contents of the certificate.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="setAuth()" type="AuthenticationOptions">
|
||||
The authentication object to use for the client. This is required unless you're using environment variables.
|
||||
</ParamField>
|
||||
|
@ -137,6 +137,10 @@ Import the SDK and create a client instance with your [Machine Identity](/docume
|
||||
The level of logs you wish to log The logs are derived from Rust, as we have written our base SDK in Rust.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="sslCertificatePath" optional>
|
||||
Optionally provide a path to a custom SSL certificate file. This can be substituted by setting the `INFISICAL_SSL_CERTIFICATE` environment variable to the contents of the certificate.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="auth" type="AuthenticationOptions">
|
||||
The authentication object to use for the client. This is required unless you're using environment variables.
|
||||
</ParamField>
|
||||
|
@ -97,16 +97,14 @@ client = InfisicalClient(ClientSettings(
|
||||
If manually set to 0, caching will be disabled, this is not recommended.
|
||||
</ParamField>
|
||||
|
||||
|
||||
<ParamField
|
||||
query="site_url"
|
||||
type="string"
|
||||
default="https://app.infisical.com"
|
||||
optional
|
||||
>
|
||||
<ParamField query="site_url" type="string" default="https://app.infisical.com" optional>
|
||||
Your self-hosted absolute site URL including the protocol (e.g. `https://app.infisical.com`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="ssl_certificate_path" optional>
|
||||
Optionally provide a path to a custom SSL certificate file. This can be substituted by setting the `INFISICAL_SSL_CERTIFICATE` environment variable to the contents of the certificate.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="auth" type="AuthenticationOptions">
|
||||
The authentication object to use for the client. This is required unless you're using environment variables.
|
||||
</ParamField>
|
||||
|
436
docs/sdks/languages/ruby.mdx
Normal file
@ -0,0 +1,436 @@
|
||||
---
|
||||
title: "Infisical Ruby SDK"
|
||||
sidebarTitle: "Ruby"
|
||||
icon: "diamond"
|
||||
---
|
||||
|
||||
|
||||
|
||||
If you're working with Ruby , the official [Infisical Ruby SDK](https://github.com/infisical/sdk) package is the easiest way to fetch and work with secrets for your application.
|
||||
|
||||
- [Ruby Package](https://rubygems.org/gems/infisical-sdk)
|
||||
- [Github Repository](https://github.com/infisical/sdk)
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```ruby
|
||||
require 'infisical-sdk'
|
||||
|
||||
# 1. Create the Infisical client
|
||||
infisical = InfisicalSDK::InfisicalClient.new('https://app.infisical.com')
|
||||
|
||||
infisical.auth.universal_auth(client_id: 'YOUR_CLIENT_ID', client_secret: 'YOUR_CLIENT_SECRET')
|
||||
|
||||
test_secret = infisical.secrets.get(
|
||||
secret_name: 'API_KEY',
|
||||
project_id: 'project-id',
|
||||
environment: 'dev'
|
||||
)
|
||||
puts "Secret: #{single_test_secret}"
|
||||
```
|
||||
|
||||
This example demonstrates how to use the Infisical Ruby SDK in a simple Ruby application. The application retrieves a secret named `API_KEY` from the `dev` environment of the `YOUR_PROJECT_ID` project.
|
||||
|
||||
<Warning>
|
||||
We do not recommend hardcoding your [Machine Identity Tokens](/platform/identities/overview). Setting it as an environment variable would be best.
|
||||
</Warning>
|
||||
|
||||
# Installation
|
||||
|
||||
```console
|
||||
$ gem install infisical-sdk
|
||||
```
|
||||
# Configuration
|
||||
|
||||
Import the SDK and create a client instance.
|
||||
|
||||
```ruby
|
||||
infisical = InfisicalSDK::InfisicalClient.new('https://app.infisical.com') # Optional parameter, default is https://api.infisical.com
|
||||
```
|
||||
|
||||
### Client parameters
|
||||
|
||||
<ParamField query="options" type="object">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="Site URL" type="string" optional>
|
||||
The URL of the Infisical API. Default is `https://api.infisical.com`.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="Cache TTL" type="string" required>
|
||||
How long the client should cache secrets for. Default is 5 minutes. Disable by setting to 0.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
### Authentication
|
||||
|
||||
The SDK supports a variety of authentication methods. The most common authentication method is Universal Auth, which uses a client ID and client secret to authenticate.
|
||||
|
||||
#### Universal Auth
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `auth.universal_auth()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` - Your machine identity client ID.
|
||||
- `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` - Your machine identity client secret.
|
||||
|
||||
**Using the SDK directly**
|
||||
```ruby
|
||||
infisical.auth.universal_auth(client_id: 'your-client-id', client_secret: 'your-client-secret')
|
||||
```
|
||||
|
||||
#### GCP ID Token Auth
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on Google Cloud Platform.
|
||||
Please [read more](/documentation/platform/identities/gcp-auth) about this authentication method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.auth.gcp_id_token_auth()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_GCP_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
|
||||
**Using the SDK directly**
|
||||
```ruby
|
||||
infisical.auth.gcp_id_token_auth(identity_id: 'MACHINE_IDENTITY_ID')
|
||||
```
|
||||
|
||||
#### GCP IAM Auth
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.auth.gcp_iam_auth()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_GCP_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
- `INFISICAL_GCP_IAM_SERVICE_ACCOUNT_KEY_FILE_PATH` - The path to your GCP service account key file.
|
||||
|
||||
**Using the SDK directly**
|
||||
```ruby
|
||||
infisical.auth.gcp_iam_auth(identity_id: 'MACHINE_IDENTITY_ID', service_account_key_file_path: 'SERVICE_ACCOUNT_KEY_FILE_PATH')
|
||||
```
|
||||
|
||||
#### AWS IAM Auth
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on AWS.
|
||||
Please [read more](/documentation/platform/identities/aws-auth) about this authentication method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.auth.aws_iam_auth()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_AWS_IAM_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
|
||||
**Using the SDK directly**
|
||||
```ruby
|
||||
infisical.auth.aws_iam_auth(identity_id: 'MACHINE_IDENTITY_ID')
|
||||
```
|
||||
|
||||
|
||||
#### Azure Auth
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on Azure.
|
||||
Please [read more](/documentation/platform/identities/azure-auth) about this authentication method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.auth.azure_auth()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_AZURE_AUTH_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
|
||||
**Using the SDK directly**
|
||||
```ruby
|
||||
infisical.auth.azure_auth(identity_id: 'MACHINE_IDENTITY_ID')
|
||||
```
|
||||
|
||||
#### Kubernetes Auth
|
||||
<Info>
|
||||
Please note that this authentication method will only work if you're running your application on Kubernetes.
|
||||
Please [read more](/documentation/platform/identities/kubernetes-auth) about this authentication method.
|
||||
</Info>
|
||||
|
||||
**Using environment variables**
|
||||
|
||||
Call `.auth.kubernetes_auth()` with empty arguments to use the following environment variables:
|
||||
|
||||
- `INFISICAL_KUBERNETES_IDENTITY_ID` - Your Infisical Machine Identity ID.
|
||||
- `INFISICAL_KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH_ENV_NAME` - The environment variable name that contains the path to the service account token. This is optional and will default to `/var/run/secrets/kubernetes.io/serviceaccount/token`.
|
||||
|
||||
**Using the SDK directly**
|
||||
```ruby
|
||||
# Service account token path will default to /var/run/secrets/kubernetes.io/serviceaccount/token if empty value is passed
|
||||
infisical.auth.kubernetes_auth(identity_id: 'MACHINE_IDENTITY_ID', service_account_token_path: nil)
|
||||
```
|
||||
|
||||
## Working with Secrets
|
||||
|
||||
### client.secrets.list(options)
|
||||
|
||||
```ruby
|
||||
secrets = infisical.secrets.list(
|
||||
project_id: 'PROJECT_ID',
|
||||
environment: 'dev',
|
||||
path: '/foo/bar',
|
||||
)
|
||||
```
|
||||
|
||||
Retrieve all secrets within the Infisical project and environment that client is connected to
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object">
|
||||
<Expandable title="properties">
|
||||
<ParamField query="environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="project_id" type="string">
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="path" type="string" optional>
|
||||
The path from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="attach_to_process_env" type="boolean" default="false" optional>
|
||||
Whether or not to set the fetched secrets to the process environment. If true, you can access the secrets like so `System.getenv("SECRET_NAME")`.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="include_imports" type="boolean" default="false" optional>
|
||||
Whether or not to include imported secrets from the current path. Read about [secret import](/documentation/platform/secret-reference)
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="recursive" type="boolean" default="false" optional>
|
||||
Whether or not to fetch secrets recursively from the specified path. Please note that there's a 20-depth limit for recursive fetching.
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="expand_secret_references" type="boolean" default="true" optional>
|
||||
Whether or not to expand secret references in the fetched secrets. Read about [secret reference](/documentation/platform/secret-reference)
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
|
||||
</ParamField>
|
||||
|
||||
### client.secrets.get(options)
|
||||
|
||||
```ruby
|
||||
secret = infisical.secrets.get(
|
||||
secret_name: 'API_KEY',
|
||||
project_id: project_id,
|
||||
environment: env_slug
|
||||
)
|
||||
```
|
||||
|
||||
Retrieve a secret from Infisical.
|
||||
|
||||
By default, `Secrets().Retrieve()` fetches and returns a shared secret.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="secret_name" type="string" required>
|
||||
The key of the secret to retrieve.
|
||||
</ParamField>
|
||||
<ParamField query="project_id" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="path" type="string" optional>
|
||||
The path from where secret should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
### client.secrets.create(options)
|
||||
|
||||
```ruby
|
||||
new_secret = infisical.secrets.create(
|
||||
secret_name: 'NEW_SECRET',
|
||||
secret_value: 'SECRET_VALUE',
|
||||
project_id: 'PROJECT_ID',
|
||||
environment: 'dev',
|
||||
)
|
||||
```
|
||||
|
||||
Create a new secret in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="secret_name" type="string" required>
|
||||
The key of the secret to create.
|
||||
</ParamField>
|
||||
<ParamField query="secret_value" type="string" required>
|
||||
The value of the secret.
|
||||
</ParamField>
|
||||
<ParamField query="secret_comment" type="string" optional>
|
||||
A comment for the secret.
|
||||
</ParamField>
|
||||
<ParamField query="project_id" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="path" type="string" optional>
|
||||
The path from where secret should be created.
|
||||
</ParamField>
|
||||
<ParamField query="type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
### client.secrets.update(options)
|
||||
|
||||
```ruby
|
||||
updated_secret = infisical.secrets.update(
|
||||
secret_name: 'SECRET_KEY_TO_UPDATE',
|
||||
secret_value: 'NEW_SECRET_VALUE',
|
||||
project_id: 'PROJECT_ID',
|
||||
environment: 'dev',
|
||||
)
|
||||
```
|
||||
|
||||
Update an existing secret in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="secret_name" type="string" required>
|
||||
The key of the secret to update.
|
||||
</ParamField>
|
||||
<ParamField query="secret_value" type="string" required>
|
||||
The new value of the secret.
|
||||
</ParamField>
|
||||
<ParamField query="skip_multiline_encoding" type="boolean" default="false" optional>
|
||||
Whether or not to skip multiline encoding for the new secret value.
|
||||
</ParamField>
|
||||
<ParamField query="project_id" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="path" type="string" optional>
|
||||
The path from where secret should be updated.
|
||||
</ParamField>
|
||||
<ParamField query="type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
### client.secrets.delete(options)
|
||||
|
||||
```ruby
|
||||
deleted_secret = infisical.secrets.delete(
|
||||
secret_name: 'SECRET_TO_DELETE',
|
||||
project_id: 'PROJECT_ID',
|
||||
environment: 'dev',
|
||||
)
|
||||
```
|
||||
|
||||
Delete a secret in Infisical.
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" optional>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="secret_name" type="string">
|
||||
The key of the secret to update.
|
||||
</ParamField>
|
||||
<ParamField query="project_id" type="string" required>
|
||||
The project ID where the secret lives in.
|
||||
</ParamField>
|
||||
<ParamField query="environment" type="string" required>
|
||||
The slug name (dev, prod, etc) of the environment from where secrets should be fetched from.
|
||||
</ParamField>
|
||||
<ParamField query="path" type="string" optional>
|
||||
The path from where secret should be deleted.
|
||||
</ParamField>
|
||||
<ParamField query="type" type="string" optional>
|
||||
The type of the secret. Valid options are "shared" or "personal". If not specified, the default value is "shared".
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
|
||||
## Cryptography
|
||||
|
||||
### Create a symmetric key
|
||||
|
||||
Create a base64-encoded, 256-bit symmetric key to be used for encryption/decryption.
|
||||
|
||||
```ruby
|
||||
key = infisical.cryptography.create_symmetric_key
|
||||
```
|
||||
|
||||
#### Returns (string)
|
||||
`key` (string): A base64-encoded, 256-bit symmetric key, that can be used for encryption/decryption purposes.
|
||||
|
||||
### Encrypt symmetric
|
||||
```ruby
|
||||
encrypted_data = infisical.cryptography.encrypt_symmetric(data: "Hello World!", key: key)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
<ParamField query="Parameters" type="object" required>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="data" type="string">
|
||||
The plaintext you want to encrypt.
|
||||
</ParamField>
|
||||
<ParamField query="key" type="string" required>
|
||||
The symmetric key to use for encryption.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
#### Returns (object)
|
||||
`tag` (string): A base64-encoded, 128-bit authentication tag.
|
||||
`iv` (string): A base64-encoded, 96-bit initialization vector.
|
||||
`ciphertext` (string): A base64-encoded, encrypted ciphertext.
|
||||
|
||||
|
||||
### Decrypt symmetric
|
||||
```ruby
|
||||
decrypted_data = infisical.cryptography.decrypt_symmetric(
|
||||
ciphertext: encrypted_data['ciphertext'],
|
||||
iv: encrypted_data['iv'],
|
||||
tag: encrypted_data['tag'],
|
||||
key: key
|
||||
)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
<ParamField query="Parameters" type="object" required>
|
||||
<Expandable title="properties">
|
||||
<ParamField query="ciphertext" type="string">
|
||||
The ciphertext you want to decrypt.
|
||||
</ParamField>
|
||||
<ParamField query="key" type="string" required>
|
||||
The symmetric key to use for encryption.
|
||||
</ParamField>
|
||||
<ParamField query="iv" type="string" required>
|
||||
The initialization vector to use for decryption.
|
||||
</ParamField>
|
||||
<ParamField query="tag" type="string" required>
|
||||
The authentication tag to use for decryption.
|
||||
</ParamField>
|
||||
</Expandable>
|
||||
</ParamField>
|
||||
|
||||
#### Returns (string)
|
||||
`Plaintext` (string): The decrypted plaintext.
|
@ -25,6 +25,10 @@ From local development to production, Infisical SDKs provide the easiest way for
|
||||
<Card href="/sdks/languages/csharp" title="C#" icon="bars" color="#368833">
|
||||
Manage secrets for your C#/.NET application on demand
|
||||
</Card>
|
||||
|
||||
<Card href="/sdks/languages/ruby" title="Ruby" icon="diamond" color="#367B99">
|
||||
Manage secrets for your Ruby application on demand
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## FAQ
|
||||
|