mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 14:28:20 +00:00
feat(api): view secret value, WIP
This commit is contained in:
+36
@@ -0,0 +1,36 @@
|
|||||||
|
import { selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import { Knex } from "knex";
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
// [["read,create","secrets",{"environment":{"$eq":"dev"}}],
|
||||||
|
// ["read,edit,create","secrets",{"environment":{"$eq":"staging"}}],
|
||||||
|
// ["read,create","secrets",{"environment":{"$eq":"prod"}},1],
|
||||||
|
// ["edit,delete,create","secret-folders",{}],
|
||||||
|
// ["read,edit,delete,create","secret-imports",{}],
|
||||||
|
// ["read,edit,delete,create","member"],
|
||||||
|
// ["read,edit,delete,create","role"],
|
||||||
|
// ["read,edit,delete,create","integrations"],
|
||||||
|
// ["read,edit","settings"],
|
||||||
|
// ["edit,delete","workspace"],
|
||||||
|
// ["read,edit,delete,create","tags"],
|
||||||
|
// ["read,create,edit,delete,sync-secrets,import-secrets,remove-secrets","secret-syncs"]]
|
||||||
|
|
||||||
|
// enum ProjectPermissionSub {
|
||||||
|
// Secrets = "secrets"
|
||||||
|
// }
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const projectRoles = await knex(TableName.ProjectRoles).select(selectAllTableCols(TableName.ProjectRoles));
|
||||||
|
|
||||||
|
for (const projectRole of projectRoles) {
|
||||||
|
const { _, permissions } = projectRole;
|
||||||
|
|
||||||
|
const parsedPermissions = JSON.parse(permissions as string) as Record<string, string>[]; // contains array of permissions.
|
||||||
|
|
||||||
|
for (const parsedPermission of parsedPermissions) {
|
||||||
|
console.log(parsedPermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { ProjectPermissionActions } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
||||||
import { RAW_SECRETS } from "@app/lib/api-docs";
|
import { RAW_SECRETS } from "@app/lib/api-docs";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -9,7 +9,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
|
|
||||||
const AccessListEntrySchema = z
|
const AccessListEntrySchema = z
|
||||||
.object({
|
.object({
|
||||||
allowedActions: z.nativeEnum(ProjectPermissionActions).array(),
|
allowedActions: z.nativeEnum(ProjectPermissionSecretActions).array(),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
membershipId: z.string(),
|
membershipId: z.string(),
|
||||||
name: z.string()
|
name: z.string()
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ export enum ProjectPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionSecretActions {
|
||||||
|
DescribeSecret = "read",
|
||||||
|
ReadValue = "readValue",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionCmekActions {
|
export enum ProjectPermissionCmekActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
@@ -115,7 +123,7 @@ export type IdentityManagementSubjectFields = {
|
|||||||
|
|
||||||
export type ProjectPermissionSet =
|
export type ProjectPermissionSet =
|
||||||
| [
|
| [
|
||||||
ProjectPermissionActions,
|
ProjectPermissionSecretActions,
|
||||||
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
||||||
]
|
]
|
||||||
| [
|
| [
|
||||||
@@ -433,7 +441,7 @@ export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
|||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
),
|
),
|
||||||
conditions: SecretConditionV1Schema.describe(
|
conditions: SecretConditionV1Schema.describe(
|
||||||
@@ -460,7 +468,7 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
|||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
),
|
),
|
||||||
conditions: SecretConditionV2Schema.describe(
|
conditions: SecretConditionV2Schema.describe(
|
||||||
@@ -517,7 +525,6 @@ const buildAdminPermissionRules = () => {
|
|||||||
|
|
||||||
// Admins get full access to everything
|
// Admins get full access to everything
|
||||||
[
|
[
|
||||||
ProjectPermissionSub.Secrets,
|
|
||||||
ProjectPermissionSub.SecretFolders,
|
ProjectPermissionSub.SecretFolders,
|
||||||
ProjectPermissionSub.SecretImports,
|
ProjectPermissionSub.SecretImports,
|
||||||
ProjectPermissionSub.SecretApproval,
|
ProjectPermissionSub.SecretApproval,
|
||||||
@@ -550,10 +557,21 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionActions.Delete
|
ProjectPermissionActions.Delete
|
||||||
],
|
],
|
||||||
el as ProjectPermissionSub
|
el
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
ProjectPermissionSecretActions.Create,
|
||||||
|
ProjectPermissionSecretActions.Edit,
|
||||||
|
ProjectPermissionSecretActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
@@ -613,10 +631,11 @@ const buildMemberPermissionRules = () => {
|
|||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Edit,
|
||||||
ProjectPermissionActions.Delete
|
ProjectPermissionSecretActions.Create,
|
||||||
|
ProjectPermissionSecretActions.Delete
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Secrets
|
ProjectPermissionSub.Secrets
|
||||||
);
|
);
|
||||||
@@ -788,7 +807,8 @@ export const projectMemberPermissions = buildMemberPermissionRules();
|
|||||||
const buildViewerPermissionRules = () => {
|
const buildViewerPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
// ? Q(Daniel): Should the viewer role be allowed to read values? Currently not allowed in permission below.
|
||||||
|
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||||
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
||||||
@@ -831,6 +851,8 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
) => {
|
) => {
|
||||||
const canWrite = permission.includes("write");
|
const canWrite = permission.includes("write");
|
||||||
const canRead = permission.includes("read");
|
const canRead = permission.includes("read");
|
||||||
|
const canReadValue = permission.includes("readValue");
|
||||||
|
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
scopes.forEach(({ secretPath, environment }) => {
|
scopes.forEach(({ secretPath, environment }) => {
|
||||||
[ProjectPermissionSub.Secrets, ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretFolders].forEach(
|
[ProjectPermissionSub.Secrets, ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretFolders].forEach(
|
||||||
@@ -860,6 +882,14 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subject === ProjectPermissionSub.Secrets && canReadValue) {
|
||||||
|
// @ts-expect-error type
|
||||||
|
can(ProjectPermissionSecretActions.ReadValue, subject as ProjectPermissionSub.Secrets, {
|
||||||
|
secretPath: { $glob: secretPath },
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
||||||
@@ -88,7 +88,12 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretDAL: TSecretDALFactory;
|
secretDAL: TSecretDALFactory;
|
||||||
secretTagDAL: Pick<
|
secretTagDAL: Pick<
|
||||||
TSecretTagDALFactory,
|
TSecretTagDALFactory,
|
||||||
"findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret" | "saveTagsToSecretV2" | "deleteTagsToSecretV2"
|
| "findManyTagsById"
|
||||||
|
| "saveTagsToSecret"
|
||||||
|
| "deleteTagsManySecret"
|
||||||
|
| "saveTagsToSecretV2"
|
||||||
|
| "deleteTagsToSecretV2"
|
||||||
|
| "find"
|
||||||
>;
|
>;
|
||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||||
@@ -914,7 +919,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1001,6 +1006,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
: keyName2BlindIndex[secretName];
|
: keyName2BlindIndex[secretName];
|
||||||
// add tags
|
// add tags
|
||||||
if (tagIds?.length) commitTagIds[keyName2BlindIndex[secretName]] = tagIds;
|
if (tagIds?.length) commitTagIds[keyName2BlindIndex[secretName]] = tagIds;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...latestSecretVersions[secretId],
|
...latestSecretVersions[secretId],
|
||||||
...el,
|
...el,
|
||||||
@@ -1363,9 +1369,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const tagsGroupById = groupBy(tags, (i) => i.id);
|
const tagsGroupById = groupBy(tags, (i) => i.id);
|
||||||
|
|
||||||
commits.forEach((commit) => {
|
commits.forEach((commit) => {
|
||||||
let action = ProjectPermissionActions.Create;
|
let action = ProjectPermissionSecretActions.Create;
|
||||||
if (commit.op === SecretOperations.Update) action = ProjectPermissionActions.Edit;
|
if (commit.op === SecretOperations.Update) action = ProjectPermissionSecretActions.Edit;
|
||||||
if (commit.op === SecretOperations.Delete) action = ProjectPermissionActions.Delete;
|
if (commit.op === SecretOperations.Delete) action = ProjectPermissionSecretActions.Delete;
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
action,
|
action,
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret
|
|||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "../permission/project-permission";
|
||||||
import { TSecretRotationDALFactory } from "./secret-rotation-dal";
|
import { TSecretRotationDALFactory } from "./secret-rotation-dal";
|
||||||
import { TSecretRotationQueueFactory } from "./secret-rotation-queue";
|
import { TSecretRotationQueueFactory } from "./secret-rotation-queue";
|
||||||
import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types";
|
import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types";
|
||||||
@@ -106,7 +110,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/se
|
|||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "../permission/project-permission";
|
||||||
import {
|
import {
|
||||||
TGetSnapshotDataDTO,
|
TGetSnapshotDataDTO,
|
||||||
TProjectSnapshotCountDTO,
|
TProjectSnapshotCountDTO,
|
||||||
@@ -97,7 +101,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -134,7 +138,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -224,7 +228,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: snapshotDetails.environment.slug,
|
environment: snapshotDetails.environment.slug,
|
||||||
secretPath: fullFolderPath
|
secretPath: fullFolderPath
|
||||||
|
|||||||
@@ -666,6 +666,7 @@ export const SECRETS = {
|
|||||||
secretPath: "The path of the secret to attach tags to.",
|
secretPath: "The path of the secret to attach tags to.",
|
||||||
type: "The type of the secret to attach tags to. (shared/personal)",
|
type: "The type of the secret to attach tags to. (shared/personal)",
|
||||||
environment: "The slug of the environment where the secret is located",
|
environment: "The slug of the environment where the secret is located",
|
||||||
|
viewSecretValue: "Whether or not to retrieve the secret value.",
|
||||||
projectSlug: "The slug of the project where the secret is located.",
|
projectSlug: "The slug of the project where the secret is located.",
|
||||||
tagSlugs: "An array of existing tag slugs to attach to the secret."
|
tagSlugs: "An array of existing tag slugs to attach to the secret."
|
||||||
},
|
},
|
||||||
@@ -689,6 +690,7 @@ export const RAW_SECRETS = {
|
|||||||
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
||||||
environment: "The slug of the environment to list secrets from.",
|
environment: "The slug of the environment to list secrets from.",
|
||||||
secretPath: "The secret path to list secrets from.",
|
secretPath: "The secret path to list secrets from.",
|
||||||
|
viewSecretValue: "Whether or not to retrieve the secret value.",
|
||||||
includeImports: "Weather to include imported secrets or not.",
|
includeImports: "Weather to include imported secrets or not.",
|
||||||
tagSlugs: "The comma separated tag slugs to filter secrets.",
|
tagSlugs: "The comma separated tag slugs to filter secrets.",
|
||||||
metadataFilter:
|
metadataFilter:
|
||||||
@@ -717,6 +719,7 @@ export const RAW_SECRETS = {
|
|||||||
secretPath: "The path of the secret to get.",
|
secretPath: "The path of the secret to get.",
|
||||||
version: "The version of the secret to get.",
|
version: "The version of the secret to get.",
|
||||||
type: "The type of the secret to get.",
|
type: "The type of the secret to get.",
|
||||||
|
viewSecretValue: "Whether or not to retrieve the secret value.",
|
||||||
includeImports: "Weather to include imported secrets or not."
|
includeImports: "Weather to include imported secrets or not."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// eslint-disable-next-line max-classes-per-file
|
||||||
|
import { AnyAbility, ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
export class DatabaseError extends Error {
|
export class DatabaseError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -59,6 +62,13 @@ export class ForbiddenRequestError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ForbiddenReadSecretError extends ForbiddenRequestError {
|
||||||
|
constructor({ error, message }: { message?: string; error?: unknown } = {}) {
|
||||||
|
super({ message, error });
|
||||||
|
this.name = "ForbiddenReadSecretError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class BadRequestError extends Error {
|
export class BadRequestError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
dynamicSecrets: SanitizedDynamicSecretSchema.extend({ environment: z.string() }).array().optional(),
|
dynamicSecrets: SanitizedDynamicSecretSchema.extend({ environment: z.string() }).array().optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
|
secretValueHidden: z.boolean(),
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
@@ -294,6 +295,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||||
secrets = await server.services.secret.getSecretsRawMultiEnv({
|
secrets = await server.services.secret.getSecretsRawMultiEnv({
|
||||||
|
viewSecretValue: true,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
@@ -393,6 +395,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
search: z.string().trim().describe(DASHBOARD.SECRET_DETAILS_LIST.search).optional(),
|
search: z.string().trim().describe(DASHBOARD.SECRET_DETAILS_LIST.search).optional(),
|
||||||
tags: z.string().trim().transform(decodeURIComponent).describe(DASHBOARD.SECRET_DETAILS_LIST.tags).optional(),
|
tags: z.string().trim().transform(decodeURIComponent).describe(DASHBOARD.SECRET_DETAILS_LIST.tags).optional(),
|
||||||
|
viewSecretValue: booleanSchema.default(true),
|
||||||
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
||||||
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
||||||
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
||||||
@@ -410,6 +413,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
dynamicSecrets: SanitizedDynamicSecretSchema.array().optional(),
|
dynamicSecrets: SanitizedDynamicSecretSchema.array().optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
|
secretValueHidden: z.boolean(),
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
@@ -600,10 +604,18 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
tagSlugs: tags
|
tagSlugs: tags
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("totalSecretCount", totalSecretCount);
|
||||||
|
console.log("adjustedOffset", adjustedOffset);
|
||||||
|
console.log("remainingLimit", remainingLimit);
|
||||||
|
|
||||||
|
console.log("will resolve to true", remainingLimit > 0 && totalSecretCount > adjustedOffset);
|
||||||
|
|
||||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||||
|
console.log("before running");
|
||||||
const secretsRaw = await server.services.secret.getSecretsRaw({
|
const secretsRaw = await server.services.secret.getSecretsRaw({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
|
viewSecretValue: req.query.viewSecretValue,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
environment,
|
environment,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@@ -617,6 +629,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
tagSlugs: tags
|
tagSlugs: tags
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("secretsRaw", secretsRaw);
|
||||||
secrets = secretsRaw.secrets;
|
secrets = secretsRaw.secrets;
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -649,6 +662,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
if (!(error instanceof ForbiddenError)) {
|
if (!(error instanceof ForbiddenError)) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -862,12 +876,14 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId: z.string().trim(),
|
projectId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||||
keys: z.string().trim().transform(decodeURIComponent)
|
keys: z.string().trim().transform(decodeURIComponent),
|
||||||
|
viewSecretValue: booleanSchema.default(true)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
|
secretValueHidden: z.boolean(),
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
@@ -886,7 +902,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { secretPath, projectId, environment } = req.query;
|
const { secretPath, projectId, environment, viewSecretValue } = req.query;
|
||||||
|
|
||||||
const keys = req.query.keys?.split(",").filter((key) => Boolean(key.trim())) ?? [];
|
const keys = req.query.keys?.split(",").filter((key) => Boolean(key.trim())) ?? [];
|
||||||
if (!keys.length) throw new BadRequestError({ message: "One or more keys required" });
|
if (!keys.length) throw new BadRequestError({ message: "One or more keys required" });
|
||||||
@@ -895,6 +911,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
viewSecretValue,
|
||||||
environment,
|
environment,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
secure: cfg.HTTPS_ENABLED
|
secure: cfg.HTTPS_ENABLED
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("access token", tokens.access);
|
||||||
|
|
||||||
return { token: tokens.access, isMfaEnabled: false };
|
return { token: tokens.access, isMfaEnabled: false };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ const SecretReferenceNode = z.object({
|
|||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
secretPath: z.string()
|
secretPath: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const convertStringBoolean = (defaultValue: boolean = false) => {
|
||||||
|
return z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default(defaultValue ? "true" : "false")
|
||||||
|
.transform((value) => value === "true");
|
||||||
|
};
|
||||||
|
|
||||||
type TSecretReferenceNode = z.infer<typeof SecretReferenceNode> & { children: TSecretReferenceNode[] };
|
type TSecretReferenceNode = z.infer<typeof SecretReferenceNode> & { children: TSecretReferenceNode[] };
|
||||||
|
|
||||||
const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReferenceNode.extend({
|
const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReferenceNode.extend({
|
||||||
@@ -45,6 +53,7 @@ const SecretNameSchema = BaseSecretNameSchema.refine(
|
|||||||
).refine((el) => !el.includes(":"), "Secret name cannot contain colon.");
|
).refine((el) => !el.includes(":"), "Secret name cannot contain colon.");
|
||||||
|
|
||||||
export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||||
|
// ! Note(Daniel): (Tags) Does not support secrets v2. Request will fail if user doesn't have read value permission.
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/tags/:secretName",
|
url: "/tags/:secretName",
|
||||||
@@ -64,6 +73,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
projectSlug: z.string().trim().describe(SECRETS.ATTACH_TAGS.projectSlug),
|
projectSlug: z.string().trim().describe(SECRETS.ATTACH_TAGS.projectSlug),
|
||||||
environment: z.string().trim().describe(SECRETS.ATTACH_TAGS.environment),
|
environment: z.string().trim().describe(SECRETS.ATTACH_TAGS.environment),
|
||||||
|
viewSecretValue: convertStringBoolean(true).describe(SECRETS.ATTACH_TAGS.viewSecretValue),
|
||||||
secretPath: z
|
secretPath: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -108,6 +118,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ! Note(Daniel): (Tags) Does not support secrets v2. Request will fail if user doesn't have read value permission.
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/tags/:secretName",
|
url: "/tags/:secretName",
|
||||||
@@ -169,6 +180,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Note(Daniel): (Secrets) Done for v2 secrets AND normal secrets GET /raw
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/raw",
|
url: "/raw",
|
||||||
@@ -247,21 +259,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
|
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
|
||||||
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
|
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
|
||||||
expandSecretReferences: z
|
viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.LIST.viewSecretValue),
|
||||||
.enum(["true", "false"])
|
expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.LIST.expand),
|
||||||
.default("false")
|
recursive: convertStringBoolean().describe(RAW_SECRETS.LIST.recursive),
|
||||||
.transform((value) => value === "true")
|
include_imports: convertStringBoolean().describe(RAW_SECRETS.LIST.includeImports),
|
||||||
.describe(RAW_SECRETS.LIST.expand),
|
|
||||||
recursive: z
|
|
||||||
.enum(["true", "false"])
|
|
||||||
.default("false")
|
|
||||||
.transform((value) => value === "true")
|
|
||||||
.describe(RAW_SECRETS.LIST.recursive),
|
|
||||||
include_imports: z
|
|
||||||
.enum(["true", "false"])
|
|
||||||
.default("false")
|
|
||||||
.transform((value) => value === "true")
|
|
||||||
.describe(RAW_SECRETS.LIST.includeImports),
|
|
||||||
tagSlugs: z
|
tagSlugs: z
|
||||||
.string()
|
.string()
|
||||||
.describe(RAW_SECRETS.LIST.tagSlugs)
|
.describe(RAW_SECRETS.LIST.tagSlugs)
|
||||||
@@ -274,6 +275,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
|
secretValueHidden: z.boolean(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
@@ -293,6 +295,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.omit({ createdAt: true, updatedAt: true })
|
.omit({ createdAt: true, updatedAt: true })
|
||||||
.extend({
|
.extend({
|
||||||
|
// secretValueHidden: z.boolean(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional()
|
secretMetadata: ResourceMetadataSchema.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
@@ -342,6 +345,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
expandSecretReferences: req.query.expandSecretReferences,
|
expandSecretReferences: req.query.expandSecretReferences,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
|
viewSecretValue: req.query.viewSecretValue,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
metadataFilter: req.query.metadataFilter,
|
metadataFilter: req.query.metadataFilter,
|
||||||
includeImports: req.query.include_imports,
|
includeImports: req.query.include_imports,
|
||||||
@@ -376,10 +380,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { secrets, imports };
|
return { secrets, imports };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!!!!!!!!!!!!!!!!!!! Note(Daniel): (Secrets) Done for v2 secrets -- GET /raw/:secretName
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/raw/:secretName",
|
url: "/raw/:secretName",
|
||||||
@@ -403,20 +409,14 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
|
||||||
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
|
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
|
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
|
||||||
expandSecretReferences: z
|
viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.GET.viewSecretValue),
|
||||||
.enum(["true", "false"])
|
expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.GET.expand),
|
||||||
.default("false")
|
include_imports: convertStringBoolean().describe(RAW_SECRETS.GET.includeImports)
|
||||||
.transform((value) => value === "true")
|
|
||||||
.describe(RAW_SECRETS.GET.expand),
|
|
||||||
include_imports: z
|
|
||||||
.enum(["true", "false"])
|
|
||||||
.default("false")
|
|
||||||
.transform((value) => value === "true")
|
|
||||||
.describe(RAW_SECRETS.GET.includeImports)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secret: secretRawSchema.extend({
|
secret: secretRawSchema.extend({
|
||||||
|
secretValueHidden: z.boolean(),
|
||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
@@ -456,6 +456,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
expandSecretReferences: req.query.expandSecretReferences,
|
expandSecretReferences: req.query.expandSecretReferences,
|
||||||
environment,
|
environment,
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
|
viewSecretValue: req.query.viewSecretValue,
|
||||||
projectSlug: workspaceSlug,
|
projectSlug: workspaceSlug,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
secretName: req.params.secretName,
|
secretName: req.params.secretName,
|
||||||
@@ -498,6 +499,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ? Note(Daniel): No modify, if user has Create permissions it will return the value they created for this secret --- POST /raw/:secretName
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/raw/:secretName",
|
url: "/raw/:secretName",
|
||||||
@@ -611,6 +613,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ? Note(Daniel): Will NOT throw an error. If the user has access to read value, it will return value.
|
||||||
|
// ? Note(Daniel): If user does NOT have access to read value, it will return <hidden-by-infisical> for the value, but succeed with update.
|
||||||
|
// !!!!! Done for both secret types. For legacy secrets, it will return <hidden-by-infisical> if no read value permission is present.
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/raw/:secretName",
|
url: "/raw/:secretName",
|
||||||
@@ -728,6 +733,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ? Note(Daniel): Will NOT throw an error. If the user has access to read value, it will return the deleted value
|
||||||
|
// ? Note(Daniel): If user does NOT have access to read value, it will return <hidden> for the value, but succeed with delete.
|
||||||
|
// !!!!! Done for both secret types. For legacy secrets, it will return <hidden> if no read value permission is present. --- /raw/:secretName
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/raw/:secretName",
|
url: "/raw/:secretName",
|
||||||
@@ -758,7 +766,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secret: secretRawSchema
|
secret: secretRawSchema.extend({
|
||||||
|
secretValueHidden: z.boolean()
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -780,6 +790,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
if (secretOperation.type === SecretProtectionType.Approval) {
|
if (secretOperation.type === SecretProtectionType.Approval) {
|
||||||
return { approval: secretOperation.approval };
|
return { approval: secretOperation.approval };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { secret } = secretOperation;
|
const { secret } = secretOperation;
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -814,6 +825,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!! Done. Will throw without the `readValue` permission, just like before.
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/",
|
url: "/",
|
||||||
@@ -928,6 +940,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!! Done. Will throw without the `readValue` permission, just like before.
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:secretName",
|
url: "/:secretName",
|
||||||
@@ -942,12 +955,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||||
|
viewSecretValue: convertStringBoolean(true),
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared),
|
type: z.nativeEnum(SecretType).default(SecretType.Shared),
|
||||||
version: z.coerce.number().optional(),
|
version: z.coerce.number().optional(),
|
||||||
include_imports: z
|
include_imports: convertStringBoolean()
|
||||||
.enum(["true", "false"])
|
|
||||||
.default("false")
|
|
||||||
.transform((value) => value === "true")
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -1009,6 +1020,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!! Done. Will work exactly like before. It will not attempt to hide the secret value, because the user creating this secret will already know the value upon creation.
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:secretName",
|
url: "/:secretName",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -1180,6 +1192,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!! Done. Will work like before, EXCEPT, if the user doesn't have the `readValue` permission, the secret value will be marked as "<hidden-by-infisical>"
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:secretName",
|
url: "/:secretName",
|
||||||
@@ -1218,6 +1231,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
z.object({
|
z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||||
z.object({
|
z.object({
|
||||||
|
secretValueHidden: z.boolean(),
|
||||||
_id: z.string(),
|
_id: z.string(),
|
||||||
workspace: z.string(),
|
workspace: z.string(),
|
||||||
environment: z.string()
|
environment: z.string()
|
||||||
@@ -1367,6 +1381,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!! Done. Will work like before, EXCEPT, if the user doesn't have the `readValue` permission, the secret value will be marked as "<hidden-by-infisical>"
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:secretName",
|
url: "/:secretName",
|
||||||
@@ -1491,6 +1506,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ? No need for update, as this endpoint does not expose any values.
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/move",
|
url: "/move",
|
||||||
@@ -1546,6 +1562,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!! Done. This will works exactly like before. It will not attempt to hide the secret value, because the user creating this secret will already know the value(s) upon creation.
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/batch",
|
url: "/batch",
|
||||||
@@ -1672,6 +1689,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!! Done. Works as before, EXCEPT if the user doesn't have the `readValue` permission, the secret value(s) will be marked as "<hidden-by-infisical>"
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/batch",
|
url: "/batch",
|
||||||
@@ -1705,7 +1723,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
secrets: SecretsSchema.omit({ secretBlindIndex: true }).extend({ secretValueHidden: z.boolean() }).array()
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -1798,6 +1816,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// !!!! Done. Works as before, EXCEPT if the user doesn't have the `readValue` permission, the secret value(s) will be marked as "<hidden-by-infisical>"
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/batch",
|
url: "/batch",
|
||||||
@@ -1820,7 +1839,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
secrets: SecretsSchema.omit({ secretBlindIndex: true })
|
||||||
|
.extend({
|
||||||
|
secretValueHidden: z.boolean()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -1912,6 +1935,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ! (Daniel): Done. Will not attempt to hide secret value because this is a create operation.
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/batch/raw",
|
url: "/batch/raw",
|
||||||
@@ -2018,6 +2042,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ! Done. Works as before, except if the user doesn't have the `readValue` permission, the secret value(s) will be marked as "<hidden-by-infisical>"
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/batch/raw",
|
url: "/batch/raw",
|
||||||
@@ -2082,7 +2107,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: secretRawSchema.array()
|
secrets: secretRawSchema.extend({ secretValueHidden: z.boolean() }).array()
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -2170,6 +2195,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ! (Daniel): Done. Works as before, except if the user doesn't have the `readValue` permission, the secret value(s) will be marked as "<hidden-by-infisical>"
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/batch/raw",
|
url: "/batch/raw",
|
||||||
@@ -2204,7 +2230,11 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: secretRawSchema.array()
|
secrets: secretRawSchema
|
||||||
|
.extend({
|
||||||
|
secretValueHidden: z.boolean()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -2262,6 +2292,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ! IMPORTANT: CHANGED BEHAVIOR -> Now this endpoint will throw a descriptive error if the user doesn't have access to the value of the secret itself.
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/raw/:secretName/secret-reference-tree",
|
url: "/raw/:secretName/secret-reference-tree",
|
||||||
@@ -2314,6 +2345,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ? No work needed, does not expose secret value.
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/backfill-secret-references",
|
url: "/backfill-secret-references",
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export type TImportDataIntoInfisicalDTO = {
|
|||||||
|
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create" | "find">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export type TExternalMigrationQueueFactoryDep = {
|
|||||||
|
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create" | "find">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findOne" | "findById">;
|
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findOne" | "findById">;
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
@@ -92,7 +96,7 @@ export const integrationServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: sourceEnvironment,
|
environment: sourceEnvironment,
|
||||||
secretPath
|
secretPath
|
||||||
@@ -175,7 +179,7 @@ export const integrationServiceFactory = ({
|
|||||||
|
|
||||||
if (environment || secretPath) {
|
if (environment || secretPath) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: newEnvironment,
|
environment: newEnvironment,
|
||||||
secretPath: newSecretPath
|
secretPath: newSecretPath
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import {
|
|||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
|
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
|
||||||
import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types";
|
import { InfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-types";
|
||||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||||
@@ -747,7 +751,10 @@ export const projectServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.Any
|
actionProjectType: ActionProjectType.Any
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
|
||||||
const project = await projectDAL.findProjectById(projectId);
|
const project = await projectDAL.findProjectById(projectId);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { getReplicationFolderName } from "@app/ee/services/secret-replication/secret-replication-service";
|
import { getReplicationFolderName } from "@app/ee/services/secret-replication/secret-replication-service";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -90,7 +94,7 @@ export const secretImportServiceFactory = ({
|
|||||||
|
|
||||||
// check if user has permission to import from target path
|
// check if user has permission to import from target path
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: data.environment,
|
environment: data.environment,
|
||||||
secretPath: data.path
|
secretPath: data.path
|
||||||
@@ -402,7 +406,7 @@ export const secretImportServiceFactory = ({
|
|||||||
|
|
||||||
// check if user has permission to import from target path
|
// check if user has permission to import from target path
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: secretImportDoc.importEnv.slug,
|
environment: secretImportDoc.importEnv.slug,
|
||||||
secretPath: secretImportDoc.importPath
|
secretPath: secretImportDoc.importPath
|
||||||
@@ -596,7 +600,7 @@ export const secretImportServiceFactory = ({
|
|||||||
const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false });
|
const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false });
|
||||||
const allowedImports = secretImports.filter((el) =>
|
const allowedImports = secretImports.filter((el) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: el.importEnv.slug,
|
environment: el.importEnv.slug,
|
||||||
secretPath: el.importPath
|
secretPath: el.importPath
|
||||||
@@ -647,7 +651,7 @@ export const secretImportServiceFactory = ({
|
|||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: expandEnvironment,
|
environment: expandEnvironment,
|
||||||
secretPath: expandSecretPath,
|
secretPath: expandSecretPath,
|
||||||
@@ -667,7 +671,7 @@ export const secretImportServiceFactory = ({
|
|||||||
|
|
||||||
const allowedImports = secretImports.filter((el) =>
|
const allowedImports = secretImports.filter((el) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: el.importEnv.slug,
|
environment: el.importEnv.slug,
|
||||||
secretPath: el.importPath
|
secretPath: el.importPath
|
||||||
@@ -683,7 +687,10 @@ export const secretImportServiceFactory = ({
|
|||||||
return importedSecrets.map((el) => ({
|
return importedSecrets.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
secrets: el.secrets.map((encryptedSecret) =>
|
secrets: el.secrets.map((encryptedSecret) =>
|
||||||
decryptSecretRaw({ ...encryptedSecret, workspace: projectId, environment, secretPath }, botKey)
|
decryptSecretRaw(
|
||||||
|
{ ...encryptedSecret, workspace: projectId, environment, secretPath, secretValueHidden: false },
|
||||||
|
botKey
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionSecretActions,
|
||||||
ProjectPermissionSecretSyncActions,
|
ProjectPermissionSecretSyncActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
@@ -179,7 +179,7 @@ export const secretSyncServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(projectPermission).throwUnlessCan(
|
ForbiddenError.from(projectPermission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath
|
||||||
@@ -270,7 +270,7 @@ export const secretSyncServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Must specify both source environment and secret path" });
|
throw new BadRequestError({ message: "Must specify both source environment and secret path" });
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: updatedEnvironment,
|
environment: updatedEnvironment,
|
||||||
secretPath: updatedSecretPath
|
secretPath: updatedSecretPath
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export const secretTagDALFactory = (db: TDbClient) => {
|
|||||||
throw new DatabaseError({ error, name: "Find all by ids" });
|
throw new DatabaseError({ error, name: "Find all by ids" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...secretTagOrm,
|
...secretTagOrm,
|
||||||
saveTagsToSecret: secretJnTagOrm.insertMany,
|
saveTagsToSecret: secretJnTagOrm.insertMany,
|
||||||
|
|||||||
@@ -102,6 +102,19 @@ export const fnSecretBulkInsert = async ({
|
|||||||
[`${TableName.SecretV2}Id` as const]: newSecretGroupedByKeyName[key][0].id
|
[`${TableName.SecretV2}Id` as const]: newSecretGroupedByKeyName[key][0].id
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const secretTags = await secretTagDAL.find({
|
||||||
|
$in: {
|
||||||
|
id: newSecretTags.map((el) => el.secret_tagsId)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretTagsWithSlugs = await secretTagDAL.find({
|
||||||
|
$in: {
|
||||||
|
id: secretTags.map((el) => el.id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const secretVersions = await secretVersionDAL.insertMany(
|
const secretVersions = await secretVersionDAL.insertMany(
|
||||||
sanitizedInputSecrets.map((el) => ({
|
sanitizedInputSecrets.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
@@ -137,6 +150,7 @@ export const fnSecretBulkInsert = async ({
|
|||||||
if (newSecretTags.length) {
|
if (newSecretTags.length) {
|
||||||
const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx);
|
const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx);
|
||||||
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
|
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
|
||||||
|
|
||||||
const newSecretVersionTags = secTags.flatMap(({ secrets_v2Id, secret_tagsId }) => ({
|
const newSecretVersionTags = secTags.flatMap(({ secrets_v2Id, secret_tagsId }) => ({
|
||||||
[`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id,
|
[`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id,
|
||||||
[`${TableName.SecretTag}Id` as const]: secret_tagsId
|
[`${TableName.SecretTag}Id` as const]: secret_tagsId
|
||||||
@@ -623,13 +637,13 @@ export const reshapeBridgeSecret = (
|
|||||||
name: string;
|
name: string;
|
||||||
}[];
|
}[];
|
||||||
secretMetadata?: ResourceMetadataDTO;
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
}
|
},
|
||||||
|
secretValueHidden?: boolean
|
||||||
) => ({
|
) => ({
|
||||||
secretKey: secret.key,
|
secretKey: secret.key,
|
||||||
secretPath,
|
secretPath,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretValue: secret.value || "",
|
|
||||||
secretComment: secret.comment || "",
|
secretComment: secret.comment || "",
|
||||||
version: secret.version,
|
version: secret.version,
|
||||||
type: secret.type,
|
type: secret.type,
|
||||||
@@ -643,5 +657,15 @@ export const reshapeBridgeSecret = (
|
|||||||
metadata: secret.metadata,
|
metadata: secret.metadata,
|
||||||
secretMetadata: secret.secretMetadata,
|
secretMetadata: secret.secretMetadata,
|
||||||
createdAt: secret.createdAt,
|
createdAt: secret.createdAt,
|
||||||
updatedAt: secret.updatedAt
|
updatedAt: secret.updatedAt,
|
||||||
|
|
||||||
|
...(secretValueHidden
|
||||||
|
? {
|
||||||
|
secretValue: "<hidden-by-infisical>",
|
||||||
|
secretValueHidden: true
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
secretValue: secret.value || "",
|
||||||
|
secretValueHidden: false
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,12 +11,16 @@ import {
|
|||||||
TSecretsV2
|
TSecretsV2
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||||
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
||||||
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenReadSecretError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { diff, groupBy } from "@app/lib/fn";
|
import { diff, groupBy } from "@app/lib/fn";
|
||||||
import { setKnexStringValue } from "@app/lib/knex";
|
import { setKnexStringValue } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@@ -252,7 +256,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
const { secretName, type, ...inputSecretData } = inputSecret;
|
const { secretName, type, ...inputSecretData } = inputSecret;
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -271,8 +275,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
const secret = await secretDAL.transaction((tx) =>
|
const secret = await secretDAL.transaction(async (tx) => {
|
||||||
fnSecretBulkInsert({
|
const [createdSecret] = await fnSecretBulkInsert({
|
||||||
folderId,
|
folderId,
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
inputSecrets: [
|
inputSecrets: [
|
||||||
@@ -302,8 +306,10 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
tx
|
tx
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
return createdSecret;
|
||||||
|
});
|
||||||
|
|
||||||
if (inputSecret.type === SecretType.Shared) {
|
if (inputSecret.type === SecretType.Shared) {
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
@@ -318,7 +324,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return reshapeBridgeSecret(projectId, environment, secretPath, {
|
return reshapeBridgeSecret(projectId, environment, secretPath, {
|
||||||
...secret[0],
|
...secret,
|
||||||
value: inputSecret.secretValue,
|
value: inputSecret.secretValue,
|
||||||
comment: inputSecret.secretComment || ""
|
comment: inputSecret.secretComment || ""
|
||||||
});
|
});
|
||||||
@@ -390,7 +396,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -407,7 +413,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
// now check with new ids
|
// now check with new ids
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -424,7 +430,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (doesNewNameSecretExist) throw new BadRequestError({ message: "Secret with the new name already exist" });
|
if (doesNewNameSecretExist) throw new BadRequestError({ message: "Secret with the new name already exist" });
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -507,11 +513,27 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return reshapeBridgeSecret(projectId, environment, secretPath, {
|
const secretValueHidden = !permission.can(
|
||||||
...updatedSecret[0],
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
value: inputSecret.secretValue || "",
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
comment: inputSecret.secretComment || ""
|
environment,
|
||||||
});
|
secretPath,
|
||||||
|
secretName: inputSecret.secretName, // ! Note(Daniel): We are checking for the EXISTING secret name, not the new secret name.
|
||||||
|
secretTags: tags?.map((el) => el.slug) // ! Note(Daniel): Same here
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return reshapeBridgeSecret(
|
||||||
|
projectId,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
{
|
||||||
|
...updatedSecret[0],
|
||||||
|
value: inputSecret.secretValue || "",
|
||||||
|
comment: inputSecret.secretComment || ""
|
||||||
|
},
|
||||||
|
secretValueHidden
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteSecret = async ({
|
const deleteSecret = async ({
|
||||||
@@ -557,7 +579,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (!secretToDelete) throw new NotFoundError({ message: "Secret not found" });
|
if (!secretToDelete) throw new NotFoundError({ message: "Secret not found" });
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionSecretActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -599,15 +621,32 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
return reshapeBridgeSecret(projectId, environment, secretPath, {
|
|
||||||
...deletedSecret[0],
|
const secretValueHidden = !permission.can(
|
||||||
value: deletedSecret[0].encryptedValue
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedValue }).toString()
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
: "",
|
environment,
|
||||||
comment: deletedSecret[0].encryptedComment
|
secretPath,
|
||||||
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedComment }).toString()
|
secretName: secretToDelete.key,
|
||||||
: ""
|
secretTags: secretToDelete.tags?.map((el) => el.slug)
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return reshapeBridgeSecret(
|
||||||
|
projectId,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
{
|
||||||
|
...deletedSecret[0],
|
||||||
|
value: deletedSecret[0].encryptedValue
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedValue }).toString()
|
||||||
|
: "",
|
||||||
|
comment: deletedSecret[0].encryptedComment
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedComment }).toString()
|
||||||
|
: ""
|
||||||
|
},
|
||||||
|
secretValueHidden
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// get unique secrets count for multiple envs
|
// get unique secrets count for multiple envs
|
||||||
@@ -635,7 +674,10 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
||||||
@@ -682,7 +724,10 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) return 0;
|
if (!folder) return 0;
|
||||||
@@ -693,7 +738,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getSecretsByFolderMappings = async (
|
const getSecretsByFolderMappings = async (
|
||||||
{ projectId, userId, filters, folderMappings }: TGetSecretsRawByFolderMappingsDTO,
|
{ projectId, userId, filters, folderMappings, filterByAction }: TGetSecretsRawByFolderMappingsDTO,
|
||||||
projectPermission: Awaited<ReturnType<typeof permissionService.getProjectPermission>>["permission"]
|
projectPermission: Awaited<ReturnType<typeof permissionService.getProjectPermission>>["permission"]
|
||||||
) => {
|
) => {
|
||||||
const groupedFolderMappings = groupBy(folderMappings, (folderMapping) => folderMapping.folderId);
|
const groupedFolderMappings = groupBy(folderMappings, (folderMapping) => folderMapping.folderId);
|
||||||
@@ -710,10 +755,13 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
if (!filterByAction) filterByAction = ProjectPermissionSecretActions.ReadValue;
|
||||||
|
|
||||||
const decryptedSecrets = secrets
|
const decryptedSecrets = secrets
|
||||||
.filter((el) =>
|
.filter((el) =>
|
||||||
projectPermission.can(
|
projectPermission.can(
|
||||||
ProjectPermissionActions.Read,
|
filterByAction as ProjectPermissionSecretActions, // ? Typescript assumes that filterByAction may be undefined, which is not true, so we are casting it to ProjectPermissionSecretActions
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: groupedFolderMappings[el.folderId][0].environment,
|
environment: groupedFolderMappings[el.folderId][0].environment,
|
||||||
secretPath: groupedFolderMappings[el.folderId][0].path,
|
secretPath: groupedFolderMappings[el.folderId][0].path,
|
||||||
@@ -722,8 +770,19 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.map((secret) =>
|
.map((secret) => {
|
||||||
reshapeBridgeSecret(
|
// Note(Daniel): This is only relevant if the filterAction isn't set to ReadValue. This is needed for the frontend.
|
||||||
|
const secretValueHidden = !projectPermission.can(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: groupedFolderMappings[secret.folderId][0].environment,
|
||||||
|
secretPath: groupedFolderMappings[secret.folderId][0].path,
|
||||||
|
secretName: secret.key,
|
||||||
|
secretTags: secret.tags.map((i) => i.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return reshapeBridgeSecret(
|
||||||
projectId,
|
projectId,
|
||||||
groupedFolderMappings[secret.folderId][0].environment,
|
groupedFolderMappings[secret.folderId][0].environment,
|
||||||
groupedFolderMappings[secret.folderId][0].path,
|
groupedFolderMappings[secret.folderId][0].path,
|
||||||
@@ -735,9 +794,10 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
comment: secret.encryptedComment
|
comment: secret.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
||||||
: ""
|
: ""
|
||||||
}
|
},
|
||||||
)
|
secretValueHidden
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return decryptedSecrets;
|
return decryptedSecrets;
|
||||||
};
|
};
|
||||||
@@ -766,7 +826,10 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
||||||
@@ -786,7 +849,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
folderMappings,
|
folderMappings,
|
||||||
filters: params,
|
filters: params,
|
||||||
userId: actorId
|
userId: actorId,
|
||||||
|
filterByAction: ProjectPermissionSecretActions.DescribeSecret
|
||||||
},
|
},
|
||||||
permission
|
permission
|
||||||
);
|
);
|
||||||
@@ -801,6 +865,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
viewSecretValue,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
includeImports,
|
includeImports,
|
||||||
recursive,
|
recursive,
|
||||||
@@ -816,7 +881,14 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath: path,
|
||||||
|
secretTags: params.tagSlugs
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
let paths: { folderId: string; path: string }[] = [];
|
let paths: { folderId: string; path: string }[] = [];
|
||||||
|
|
||||||
@@ -854,28 +926,58 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecrets = secrets
|
const decryptedSecrets = secrets
|
||||||
.filter((el) =>
|
.filter((el) => {
|
||||||
permission.can(
|
if (viewSecretValue) {
|
||||||
ProjectPermissionActions.Read,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath: groupedPaths[el.folderId][0].path,
|
||||||
|
secretName: el.key,
|
||||||
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return permission.can(
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath: groupedPaths[el.folderId][0].path,
|
secretPath: groupedPaths[el.folderId][0].path,
|
||||||
secretName: el.key,
|
secretName: el.key,
|
||||||
secretTags: el.tags.map((i) => i.slug)
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
)
|
})
|
||||||
.map((secret) =>
|
.map((secret) => {
|
||||||
reshapeBridgeSecret(projectId, environment, groupedPaths[secret.folderId][0].path, {
|
const secretValueHidden =
|
||||||
...secret,
|
!viewSecretValue ||
|
||||||
value: secret.encryptedValue
|
!permission.can(
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
: "",
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
comment: secret.encryptedComment
|
environment,
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
secretPath: groupedPaths[secret.folderId][0].path,
|
||||||
: ""
|
secretName: secret.key,
|
||||||
})
|
secretTags: secret.tags.map((i) => i.slug)
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return reshapeBridgeSecret(
|
||||||
|
projectId,
|
||||||
|
environment,
|
||||||
|
groupedPaths[secret.folderId][0].path,
|
||||||
|
{
|
||||||
|
...secret,
|
||||||
|
value: secret.encryptedValue
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||||
|
: "",
|
||||||
|
comment: secret.encryptedComment
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
||||||
|
: ""
|
||||||
|
},
|
||||||
|
secretValueHidden
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const { expandSecretReferences } = expandSecretReferencesFactory({
|
const { expandSecretReferences } = expandSecretReferencesFactory({
|
||||||
projectId,
|
projectId,
|
||||||
@@ -883,8 +985,9 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
||||||
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
||||||
|
// ? Question(Daniel): Will throw an error if the user doesn't have access to any of the expanded secrets.
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: expandEnvironment,
|
environment: expandEnvironment,
|
||||||
secretPath: expandSecretPath,
|
secretPath: expandSecretPath,
|
||||||
@@ -929,16 +1032,28 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
||||||
permission.can(
|
return (
|
||||||
ProjectPermissionActions.Read,
|
permission.can(
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
environment: expandEnvironment,
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
secretPath: expandSecretPath,
|
environment: expandEnvironment,
|
||||||
secretName: expandSecretKey,
|
secretPath: expandSecretPath,
|
||||||
secretTags: expandSecretTags
|
secretName: expandSecretKey,
|
||||||
})
|
secretTags: expandSecretTags
|
||||||
)
|
})
|
||||||
|
) &&
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: expandEnvironment,
|
||||||
|
secretPath: expandSecretPath,
|
||||||
|
secretName: expandSecretKey,
|
||||||
|
secretTags: expandSecretTags
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -958,6 +1073,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
type,
|
type,
|
||||||
secretName,
|
secretName,
|
||||||
version,
|
version,
|
||||||
|
viewSecretValue,
|
||||||
includeImports,
|
includeImports,
|
||||||
expandSecretReferences: shouldExpandSecretReferences
|
expandSecretReferences: shouldExpandSecretReferences
|
||||||
}: TGetASecretDTO) => {
|
}: TGetASecretDTO) => {
|
||||||
@@ -1019,7 +1135,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
));
|
));
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath: path,
|
secretPath: path,
|
||||||
@@ -1028,26 +1144,31 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// this will throw if the user doesn't have read value permission no matter what
|
||||||
|
// because if its an expansion, it will fully depend on the value.
|
||||||
const { expandSecretReferences } = expandSecretReferencesFactory({
|
const { expandSecretReferences } = expandSecretReferencesFactory({
|
||||||
projectId,
|
projectId,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
||||||
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
||||||
permission.can(
|
return permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: expandEnvironment,
|
environment: expandEnvironment,
|
||||||
secretPath: expandSecretPath,
|
secretPath: expandSecretPath,
|
||||||
secretName: expandSecretKey,
|
secretName: expandSecretKey,
|
||||||
secretTags: expandSecretTags
|
secretTags: expandSecretTags
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// now if secret is not found
|
// now if secret is not found
|
||||||
// then search for imported secrets
|
// then search for imported secrets
|
||||||
// here we consider the import order also thus starting from bottom
|
// here we consider the import order also thus starting from bottom
|
||||||
|
|
||||||
|
// currently filters out the secrets that the user doesn't have access to read value on
|
||||||
if (!secret && includeImports) {
|
if (!secret && includeImports) {
|
||||||
const secretImports = await secretImportDAL.find({ folderId, isReplication: false });
|
const secretImports = await secretImportDAL.find({ folderId, isReplication: false });
|
||||||
const importedSecrets = await fnSecretsV2FromImports({
|
const importedSecrets = await fnSecretsV2FromImports({
|
||||||
@@ -1057,16 +1178,28 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined,
|
expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined,
|
||||||
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => {
|
||||||
permission.can(
|
return (
|
||||||
ProjectPermissionActions.Read,
|
permission.can(
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
environment: expandEnvironment,
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
secretPath: expandSecretPath,
|
environment: expandEnvironment,
|
||||||
secretName: expandSecretKey,
|
secretPath: expandSecretPath,
|
||||||
secretTags: expandSecretTags
|
secretName: expandSecretKey,
|
||||||
})
|
secretTags: expandSecretTags
|
||||||
)
|
})
|
||||||
|
) &&
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: expandEnvironment,
|
||||||
|
secretPath: expandSecretPath,
|
||||||
|
secretName: expandSecretKey,
|
||||||
|
secretTags: expandSecretTags
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
||||||
@@ -1099,13 +1232,41 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretValue = expandedSecretValue || "";
|
secretValue = expandedSecretValue || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return reshapeBridgeSecret(projectId, environment, path, {
|
let secretValueHidden = true;
|
||||||
...secret,
|
|
||||||
value: secretValue,
|
if (viewSecretValue) {
|
||||||
comment: secret.encryptedComment
|
if (
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
!permission.can(
|
||||||
: ""
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
});
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath: path,
|
||||||
|
secretName,
|
||||||
|
secretTags: (secret?.tags || []).map((el) => el.slug)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new ForbiddenReadSecretError({
|
||||||
|
message: `You do not have permission to view secret value on secret with name '${secretName}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
secretValueHidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reshapeBridgeSecret(
|
||||||
|
projectId,
|
||||||
|
environment,
|
||||||
|
path,
|
||||||
|
{
|
||||||
|
...secret,
|
||||||
|
value: secretValue,
|
||||||
|
comment: secret.encryptedComment
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
||||||
|
: ""
|
||||||
|
},
|
||||||
|
secretValueHidden
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createManySecret = async ({
|
const createManySecret = async ({
|
||||||
@@ -1173,7 +1334,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
inputSecrets.forEach((el) => {
|
inputSecrets.forEach((el) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -1201,8 +1362,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
||||||
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
|
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
|
||||||
|
|
||||||
const newSecrets = await secretDAL.transaction(async (tx) =>
|
const newSecrets = await secretDAL.transaction(async (tx) => {
|
||||||
fnSecretBulkInsert({
|
const createdSecrets = await fnSecretBulkInsert({
|
||||||
inputSecrets: inputSecrets.map((el) => {
|
inputSecrets: inputSecrets.map((el) => {
|
||||||
const references = secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences;
|
const references = secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences;
|
||||||
|
|
||||||
@@ -1231,8 +1392,19 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
tx
|
tx
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
const secs = await secretDAL.find(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
id: createdSecrets.map((el) => el.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
|
return secs;
|
||||||
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
@@ -1244,13 +1416,29 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
environmentSlug: folder.environment.slug
|
environmentSlug: folder.environment.slug
|
||||||
});
|
});
|
||||||
|
|
||||||
return newSecrets.map((el) =>
|
return newSecrets.map((el) => {
|
||||||
reshapeBridgeSecret(projectId, environment, secretPath, {
|
const secretValueHidden = !permission.can(
|
||||||
...el,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
environment,
|
||||||
})
|
secretPath,
|
||||||
);
|
secretName: el.key,
|
||||||
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return reshapeBridgeSecret(
|
||||||
|
projectId,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
{
|
||||||
|
...el,
|
||||||
|
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
|
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||||
|
},
|
||||||
|
secretValueHidden
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateManySecret = async ({
|
const updateManySecret = async ({
|
||||||
@@ -1293,7 +1481,17 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
||||||
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
|
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
|
||||||
|
|
||||||
const updatedSecrets: Array<TSecretsV2 & { secretPath: string }> = [];
|
const updatedSecrets: Array<
|
||||||
|
TSecretsV2 & {
|
||||||
|
secretPath: string;
|
||||||
|
tags: {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
color?: string | null;
|
||||||
|
name: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
> = [];
|
||||||
await secretDAL.transaction(async (tx) => {
|
await secretDAL.transaction(async (tx) => {
|
||||||
for await (const folder of folders) {
|
for await (const folder of folders) {
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||||
@@ -1344,7 +1542,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
secretsToUpdateInDB.forEach((el) => {
|
secretsToUpdateInDB.forEach((el) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -1364,7 +1562,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
if (updateMode === SecretUpdateMode.Upsert) {
|
if (updateMode === SecretUpdateMode.Upsert) {
|
||||||
secretsToCreate.forEach((el) => {
|
secretsToCreate.forEach((el) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -1378,7 +1576,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
// check again to avoid non authorized tags are removed
|
// check again to avoid non authorized tags are removed
|
||||||
secretsToUpdate.forEach((el) => {
|
secretsToUpdate.forEach((el) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -1430,7 +1628,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
secretsWithNewName.forEach((el) => {
|
secretsWithNewName.forEach((el) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -1455,7 +1653,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
await $validateSecretReferences(projectId, permission, secretReferences, tx);
|
await $validateSecretReferences(projectId, permission, secretReferences, tx);
|
||||||
|
|
||||||
const bulkUpdatedSecrets = await fnSecretBulkUpdate({
|
const bulkUpdatedSecretsRes = await fnSecretBulkUpdate({
|
||||||
folderId,
|
folderId,
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
tx,
|
tx,
|
||||||
@@ -1492,9 +1690,21 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
resourceMetadataDAL
|
resourceMetadataDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const bulkUpdatedSecrets = await secretDAL.find(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
id: bulkUpdatedSecretsRes.map((el) => el.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tx
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
updatedSecrets.push(...bulkUpdatedSecrets.map((el) => ({ ...el, secretPath: folder.path })));
|
updatedSecrets.push(...bulkUpdatedSecrets.map((el) => ({ ...el, secretPath: folder.path })));
|
||||||
if (updateMode === SecretUpdateMode.Upsert) {
|
if (updateMode === SecretUpdateMode.Upsert) {
|
||||||
const bulkInsertedSecrets = await fnSecretBulkInsert({
|
const bulkInsertedSecretsRes = await fnSecretBulkInsert({
|
||||||
inputSecrets: secretsToCreate.map((el) => {
|
inputSecrets: secretsToCreate.map((el) => {
|
||||||
const references = secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences;
|
const references = secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences;
|
||||||
|
|
||||||
@@ -1524,6 +1734,16 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const bulkInsertedSecrets = await secretDAL.find(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
id: bulkInsertedSecretsRes.map((el) => el.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
updatedSecrets.push(...bulkInsertedSecrets.map((el) => ({ ...el, secretPath: folder.path })));
|
updatedSecrets.push(...bulkInsertedSecrets.map((el) => ({ ...el, secretPath: folder.path })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1545,13 +1765,33 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return updatedSecrets.map((el) =>
|
return updatedSecrets.map((el) => {
|
||||||
reshapeBridgeSecret(projectId, environment, el.secretPath, {
|
const secretValueHidden = !permission.can(
|
||||||
...el,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
environment,
|
||||||
})
|
secretPath: el.secretPath,
|
||||||
);
|
secretName: el.key,
|
||||||
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...reshapeBridgeSecret(
|
||||||
|
projectId,
|
||||||
|
environment,
|
||||||
|
el.secretPath,
|
||||||
|
{
|
||||||
|
...el,
|
||||||
|
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
|
comment: el.encryptedComment
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||||
|
: ""
|
||||||
|
},
|
||||||
|
secretValueHidden
|
||||||
|
)
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteManySecret = async ({
|
const deleteManySecret = async ({
|
||||||
@@ -1613,7 +1853,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
secretsToDelete.forEach((el) => {
|
secretsToDelete.forEach((el) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionSecretActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -1652,13 +1892,35 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
return secretsDeleted.map((el) =>
|
return secretsDeleted.map((el) => {
|
||||||
reshapeBridgeSecret(projectId, environment, secretPath, {
|
const secretToDeleteMatch = secretsToDelete.find(
|
||||||
...el,
|
(i) => i.key === el.key && (i.type || SecretType.Shared) === el.type
|
||||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
);
|
||||||
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
|
||||||
})
|
const secretValueHidden =
|
||||||
);
|
!secretToDeleteMatch ||
|
||||||
|
!permission.can(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: el.key,
|
||||||
|
secretTags: secretToDeleteMatch.tags?.map((i) => i.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return reshapeBridgeSecret(
|
||||||
|
projectId,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
{
|
||||||
|
...el,
|
||||||
|
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
|
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||||
|
},
|
||||||
|
secretValueHidden
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretVersions = async ({
|
const getSecretVersions = async ({
|
||||||
@@ -1689,6 +1951,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId: folder.projectId
|
projectId: folder.projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] });
|
const secretVersions = await secretVersionDAL.find({ secretId }, { offset, limit, sort: [["createdAt", "desc"]] });
|
||||||
return secretVersions.map((el) =>
|
return secretVersions.map((el) =>
|
||||||
reshapeBridgeSecret(folder.projectId, folder.environment.envSlug, "/", {
|
reshapeBridgeSecret(folder.projectId, folder.environment.envSlug, "/", {
|
||||||
@@ -1793,7 +2056,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
sourceSecrets.forEach((secret) => {
|
sourceSecrets.forEach((secret) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionSecretActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: sourceEnvironment,
|
environment: sourceEnvironment,
|
||||||
secretPath: sourceSecretPath,
|
secretPath: sourceSecretPath,
|
||||||
@@ -1876,7 +2139,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
// permission check whether can create or edit the ones in the destination folder
|
// permission check whether can create or edit the ones in the destination folder
|
||||||
locallyCreatedSecrets.forEach((secret) => {
|
locallyCreatedSecrets.forEach((secret) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: destinationEnvironment,
|
environment: destinationEnvironment,
|
||||||
secretPath: destinationEnvironment,
|
secretPath: destinationEnvironment,
|
||||||
@@ -1888,7 +2151,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
locallyUpdatedSecrets.forEach((secret) => {
|
locallyUpdatedSecrets.forEach((secret) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: destinationEnvironment,
|
environment: destinationEnvironment,
|
||||||
secretPath: destinationEnvironment,
|
secretPath: destinationEnvironment,
|
||||||
@@ -2125,7 +2388,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2149,7 +2412,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -2158,7 +2421,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const secretValue = secret.encryptedValue
|
const decryptedSecretValue = secret.encryptedValue
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
@@ -2169,7 +2432,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
||||||
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretName, expandSecretTags) =>
|
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretName, expandSecretTags) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: expandEnvironment,
|
environment: expandEnvironment,
|
||||||
secretPath: expandSecretPath,
|
secretPath: expandSecretPath,
|
||||||
@@ -2179,10 +2442,26 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
!permission.can(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName,
|
||||||
|
secretTags: (secret?.tags || []).map((el) => el.slug)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: `Unable to get secret reference tree for secret with key '${secretName}', because you don't have permission to view secret value.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { expandedValue, stackTrace } = await getExpandedSecretStackTrace({
|
const { expandedValue, stackTrace } = await getExpandedSecretStackTrace({
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
value: secretValue
|
value: decryptedSecretValue
|
||||||
});
|
});
|
||||||
|
|
||||||
return { tree: stackTrace, value: expandedValue };
|
return { tree: stackTrace, value: expandedValue };
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { SecretType, TSecretsV2, TSecretsV2Insert, TSecretsV2Update } from "@app/db/schemas";
|
import { SecretType, TSecretsV2, TSecretsV2Insert, TSecretsV2Update } from "@app/db/schemas";
|
||||||
|
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
||||||
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
|
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||||
@@ -36,6 +37,7 @@ export type TGetSecretsDTO = {
|
|||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
tagSlugs?: string[];
|
tagSlugs?: string[];
|
||||||
|
viewSecretValue: boolean;
|
||||||
metadataFilter?: {
|
metadataFilter?: {
|
||||||
key?: string;
|
key?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
@@ -57,6 +59,7 @@ export type TGetASecretDTO = {
|
|||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
version?: number;
|
version?: number;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
viewSecretValue: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCreateSecretDTO = TProjectPermission & {
|
export type TCreateSecretDTO = TProjectPermission & {
|
||||||
@@ -166,7 +169,7 @@ export type TFnSecretBulkInsert = {
|
|||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "find">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -190,7 +193,7 @@ export type TFnSecretBulkUpdate = {
|
|||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2" | "find">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
};
|
};
|
||||||
@@ -332,4 +335,5 @@ export type TGetSecretsRawByFolderMappingsDTO = {
|
|||||||
folderMappings: { folderId: string; path: string; environment: string }[];
|
folderMappings: { folderId: string; path: string; environment: string }[];
|
||||||
userId: string;
|
userId: string;
|
||||||
filters: TFindSecretsByFolderIdsFilter;
|
filters: TFindSecretsByFolderIdsFilter;
|
||||||
|
filterByAction?: ProjectPermissionSecretActions;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
TSecrets
|
TSecrets
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import {
|
import {
|
||||||
buildSecretBlindIndexFromName,
|
buildSecretBlindIndexFromName,
|
||||||
@@ -190,7 +190,7 @@ export const recursivelyGetSecretPaths = ({
|
|||||||
const allowedPaths = paths.filter(
|
const allowedPaths = paths.filter(
|
||||||
(folder) =>
|
(folder) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath: folder.path
|
secretPath: folder.path
|
||||||
@@ -344,6 +344,7 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD
|
|||||||
|
|
||||||
export const decryptSecretRaw = (
|
export const decryptSecretRaw = (
|
||||||
secret: TSecrets & {
|
secret: TSecrets & {
|
||||||
|
secretValueHidden: boolean;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
@@ -362,12 +363,14 @@ export const decryptSecretRaw = (
|
|||||||
key
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
const secretValue = !secret.secretValueHidden
|
||||||
ciphertext: secret.secretValueCiphertext,
|
? decryptSymmetric128BitHexKeyUTF8({
|
||||||
iv: secret.secretValueIV,
|
ciphertext: secret.secretValueCiphertext,
|
||||||
tag: secret.secretValueTag,
|
iv: secret.secretValueIV,
|
||||||
key
|
tag: secret.secretValueTag,
|
||||||
});
|
key
|
||||||
|
})
|
||||||
|
: "<hidden-by-infisical>";
|
||||||
|
|
||||||
let secretComment = "";
|
let secretComment = "";
|
||||||
|
|
||||||
@@ -385,6 +388,7 @@ export const decryptSecretRaw = (
|
|||||||
secretPath: secret.secretPath,
|
secretPath: secret.secretPath,
|
||||||
workspace: secret.workspace,
|
workspace: secret.workspace,
|
||||||
environment: secret.environment,
|
environment: secret.environment,
|
||||||
|
secretValueHidden: secret.secretValueHidden,
|
||||||
secretValue,
|
secretValue,
|
||||||
secretComment,
|
secretComment,
|
||||||
version: secret.version,
|
version: secret.version,
|
||||||
@@ -1197,3 +1201,25 @@ export const fnDeleteProjectSecretReminders = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const conditionallyHideSecretValue = (
|
||||||
|
shouldHideValue: boolean,
|
||||||
|
{
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag
|
||||||
|
}: {
|
||||||
|
secretValueCiphertext: string;
|
||||||
|
secretValueIV: string;
|
||||||
|
secretValueTag: string;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const hiddenPlaceholder = "hidden-by-infisical>";
|
||||||
|
|
||||||
|
return {
|
||||||
|
secretValueCiphertext: shouldHideValue ? hiddenPlaceholder : secretValueCiphertext,
|
||||||
|
secretValueIV: shouldHideValue ? hiddenPlaceholder : secretValueIV,
|
||||||
|
secretValueTag: shouldHideValue ? hiddenPlaceholder : secretValueTag,
|
||||||
|
secretValueHidden: shouldHideValue
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||||
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
||||||
@@ -48,6 +52,7 @@ import { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bri
|
|||||||
import { TGetSecretReferencesTreeDTO } from "../secret-v2-bridge/secret-v2-bridge-types";
|
import { TGetSecretReferencesTreeDTO } from "../secret-v2-bridge/secret-v2-bridge-types";
|
||||||
import { TSecretDALFactory } from "./secret-dal";
|
import { TSecretDALFactory } from "./secret-dal";
|
||||||
import {
|
import {
|
||||||
|
conditionallyHideSecretValue,
|
||||||
decryptSecretRaw,
|
decryptSecretRaw,
|
||||||
fnSecretBlindIndexCheck,
|
fnSecretBlindIndexCheck,
|
||||||
fnSecretBulkDelete,
|
fnSecretBulkDelete,
|
||||||
@@ -204,7 +209,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -322,7 +327,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -444,7 +449,22 @@ export const secretServiceFactory = ({
|
|||||||
environmentSlug: folder.environment.slug
|
environmentSlug: folder.environment.slug
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { ...updatedSecret[0], workspace: projectId, environment, secretPath: path };
|
|
||||||
|
const secretValueHidden = !permission.can(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath: path
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...updatedSecret[0],
|
||||||
|
...conditionallyHideSecretValue(secretValueHidden, updatedSecret[0]),
|
||||||
|
workspace: projectId,
|
||||||
|
environment,
|
||||||
|
secretPath: path
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteSecret = async ({
|
const deleteSecret = async ({
|
||||||
@@ -467,7 +487,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionSecretActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -540,7 +560,19 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...deletedSecret[0], _id: deletedSecret[0].id, workspace: projectId, environment, secretPath: path };
|
const secretValueHidden = !permission.can(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...deletedSecret[0],
|
||||||
|
...conditionallyHideSecretValue(secretValueHidden, deletedSecret[0]),
|
||||||
|
_id: deletedSecret[0].id,
|
||||||
|
workspace: projectId,
|
||||||
|
environment,
|
||||||
|
secretPath: path
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecrets = async ({
|
const getSecrets = async ({
|
||||||
@@ -589,7 +621,7 @@ export const secretServiceFactory = ({
|
|||||||
paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p }));
|
paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p }));
|
||||||
} else {
|
} else {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -614,7 +646,7 @@ export const secretServiceFactory = ({
|
|||||||
actor === ActorType.SERVICE
|
actor === ActorType.SERVICE
|
||||||
? true
|
? true
|
||||||
: permission.can(
|
: permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: importEnv.slug,
|
environment: importEnv.slug,
|
||||||
secretPath: importPath
|
secretPath: importPath
|
||||||
@@ -671,7 +703,7 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
@@ -721,7 +753,7 @@ export const secretServiceFactory = ({
|
|||||||
actor === ActorType.SERVICE
|
actor === ActorType.SERVICE
|
||||||
? true
|
? true
|
||||||
: permission.can(
|
: permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: importEnv.slug,
|
environment: importEnv.slug,
|
||||||
secretPath: importPath
|
secretPath: importPath
|
||||||
@@ -739,6 +771,7 @@ export const secretServiceFactory = ({
|
|||||||
if (secretBlindIndex === importedSecrets[i].secrets[j].secretBlindIndex) {
|
if (secretBlindIndex === importedSecrets[i].secrets[j].secretBlindIndex) {
|
||||||
return {
|
return {
|
||||||
...importedSecrets[i].secrets[j],
|
...importedSecrets[i].secrets[j],
|
||||||
|
secretValueHidden: false,
|
||||||
workspace: projectId,
|
workspace: projectId,
|
||||||
environment: importedSecrets[i].environment,
|
environment: importedSecrets[i].environment,
|
||||||
secretPath: importedSecrets[i].secretPath
|
secretPath: importedSecrets[i].secretPath
|
||||||
@@ -749,7 +782,13 @@ export const secretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
if (!secret) throw new NotFoundError({ message: `Secret with name '${secretName}' not found` });
|
if (!secret) throw new NotFoundError({ message: `Secret with name '${secretName}' not found` });
|
||||||
|
|
||||||
return { ...secret, workspace: projectId, environment, secretPath: path };
|
return {
|
||||||
|
...secret,
|
||||||
|
secretValueHidden: false, // Always false because we check permission at the beginning of the function
|
||||||
|
workspace: projectId,
|
||||||
|
environment,
|
||||||
|
secretPath: path
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const createManySecret = async ({
|
const createManySecret = async ({
|
||||||
@@ -771,7 +810,7 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -859,7 +898,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -901,8 +940,8 @@ export const secretServiceFactory = ({
|
|||||||
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "One or more tags not found" });
|
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "One or more tags not found" });
|
||||||
|
|
||||||
const references = await getSecretReference(projectId);
|
const references = await getSecretReference(projectId);
|
||||||
const secrets = await secretDAL.transaction(async (tx) =>
|
const secrets = await secretDAL.transaction(async (tx) => {
|
||||||
fnSecretBulkUpdate({
|
const updatedSecrets = await fnSecretBulkUpdate({
|
||||||
folderId,
|
folderId,
|
||||||
projectId,
|
projectId,
|
||||||
tx,
|
tx,
|
||||||
@@ -932,8 +971,18 @@ export const secretServiceFactory = ({
|
|||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL
|
secretVersionTagDAL
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
const secretValueHidden = !permission.can(
|
||||||
|
ProjectPermissionSecretActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
return updatedSecrets.map((secret) => ({
|
||||||
|
...secret,
|
||||||
|
...conditionallyHideSecretValue(secretValueHidden, secret)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
@@ -967,7 +1016,7 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionSecretActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1019,7 +1068,15 @@ export const secretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return secrets;
|
const secretValueHidden = !permission.can(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
return secrets.map((secret) => ({
|
||||||
|
...secret,
|
||||||
|
...conditionallyHideSecretValue(secretValueHidden, secret)
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
@@ -1180,6 +1237,7 @@ export const secretServiceFactory = ({
|
|||||||
secretName,
|
secretName,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
environment,
|
environment,
|
||||||
|
viewSecretValue: false,
|
||||||
type: "shared"
|
type: "shared"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1194,10 +1252,11 @@ export const secretServiceFactory = ({
|
|||||||
| (typeof groupPermissions)[number]
|
| (typeof groupPermissions)[number]
|
||||||
) => {
|
) => {
|
||||||
const allowedActions = [
|
const allowedActions = [
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Delete,
|
||||||
ProjectPermissionActions.Edit
|
ProjectPermissionSecretActions.Create,
|
||||||
|
ProjectPermissionSecretActions.Edit
|
||||||
].filter((action) =>
|
].filter((action) =>
|
||||||
entityPermission.permission.can(
|
entityPermission.permission.can(
|
||||||
action,
|
action,
|
||||||
@@ -1234,6 +1293,7 @@ export const secretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
|
viewSecretValue,
|
||||||
environment,
|
environment,
|
||||||
includeImports,
|
includeImports,
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
@@ -1249,6 +1309,7 @@ export const secretServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
viewSecretValue,
|
||||||
environment,
|
environment,
|
||||||
path,
|
path,
|
||||||
recursive,
|
recursive,
|
||||||
@@ -1285,14 +1346,20 @@ export const secretServiceFactory = ({
|
|||||||
recursive
|
recursive
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
|
const decryptedSecrets = secrets.map((el) => decryptSecretRaw({ ...el, secretValueHidden: false }, botKey));
|
||||||
const filteredSecrets = tagSlugs.length
|
const filteredSecrets = tagSlugs.length
|
||||||
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
|
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
|
||||||
: decryptedSecrets;
|
: decryptedSecrets;
|
||||||
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
||||||
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
||||||
decryptSecretRaw(
|
decryptSecretRaw(
|
||||||
{ ...sec, environment: el.environment, workspace: projectId, secretPath: el.secretPath },
|
{
|
||||||
|
...sec,
|
||||||
|
environment: el.environment,
|
||||||
|
workspace: projectId,
|
||||||
|
secretPath: el.secretPath,
|
||||||
|
secretValueHidden: false
|
||||||
|
},
|
||||||
botKey
|
botKey
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -1387,6 +1454,7 @@ export const secretServiceFactory = ({
|
|||||||
path,
|
path,
|
||||||
actor,
|
actor,
|
||||||
environment,
|
environment,
|
||||||
|
viewSecretValue,
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
projectSlug,
|
projectSlug,
|
||||||
@@ -1406,6 +1474,7 @@ export const secretServiceFactory = ({
|
|||||||
includeImports,
|
includeImports,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
path,
|
path,
|
||||||
|
viewSecretValue,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -1436,6 +1505,7 @@ export const secretServiceFactory = ({
|
|||||||
message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`,
|
message: `Project bot for project with ID '${projectId}' not found. Please upgrade your project.`,
|
||||||
name: "bot_not_found_error"
|
name: "bot_not_found_error"
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecret = decryptSecretRaw(encryptedSecret, botKey);
|
const decryptedSecret = decryptSecretRaw(encryptedSecret, botKey);
|
||||||
|
|
||||||
if (expandSecretReferences) {
|
if (expandSecretReferences) {
|
||||||
@@ -1454,7 +1524,10 @@ export const secretServiceFactory = ({
|
|||||||
decryptedSecret.secretValue = expandedSecretValue || "";
|
decryptedSecret.secretValue = expandedSecretValue || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return { secretMetadata: undefined, ...decryptedSecret };
|
return {
|
||||||
|
secretMetadata: undefined,
|
||||||
|
...decryptedSecret
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const createSecretRaw = async ({
|
const createSecretRaw = async ({
|
||||||
@@ -1605,7 +1678,16 @@ export const secretServiceFactory = ({
|
|||||||
tags: tagIds
|
tags: tagIds
|
||||||
});
|
});
|
||||||
|
|
||||||
return { type: SecretProtectionType.Direct as const, secret: decryptSecretRaw(secret, botKey) };
|
return {
|
||||||
|
type: SecretProtectionType.Direct as const,
|
||||||
|
secret: decryptSecretRaw(
|
||||||
|
{
|
||||||
|
...secret,
|
||||||
|
secretValueHidden: false
|
||||||
|
},
|
||||||
|
botKey
|
||||||
|
)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSecretRaw = async ({
|
const updateSecretRaw = async ({
|
||||||
@@ -2001,7 +2083,7 @@ export const secretServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
type: SecretProtectionType.Direct as const,
|
type: SecretProtectionType.Direct as const,
|
||||||
secrets: secrets.map((secret) =>
|
secrets: secrets.map((secret) =>
|
||||||
decryptSecretRaw({ ...secret, workspace: projectId, environment, secretPath }, botKey)
|
decryptSecretRaw({ ...secret, workspace: projectId, environment, secretPath, secretValueHidden: false }, botKey)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -2307,6 +2389,7 @@ export const secretServiceFactory = ({
|
|||||||
return secretVersions.map((el) =>
|
return secretVersions.map((el) =>
|
||||||
decryptSecretRaw(
|
decryptSecretRaw(
|
||||||
{
|
{
|
||||||
|
secretValueHidden: false,
|
||||||
...el,
|
...el,
|
||||||
workspace: folder.projectId,
|
workspace: folder.projectId,
|
||||||
environment: folder.environment.envSlug,
|
environment: folder.environment.envSlug,
|
||||||
@@ -2340,7 +2423,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2446,7 +2529,7 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2637,29 +2720,33 @@ export const secretServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
const permissionChecks = [
|
||||||
ProjectPermissionActions.Delete,
|
{
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
action: ProjectPermissionSecretActions.Delete,
|
||||||
environment: sourceEnvironment,
|
subject: {
|
||||||
secretPath: sourceSecretPath
|
environment: sourceEnvironment,
|
||||||
})
|
secretPath: sourceSecretPath
|
||||||
);
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: ProjectPermissionSecretActions.Create,
|
||||||
|
subject: {
|
||||||
|
environment: destinationEnvironment,
|
||||||
|
secretPath: destinationSecretPath
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: ProjectPermissionSecretActions.Edit,
|
||||||
|
subject: {
|
||||||
|
environment: destinationEnvironment,
|
||||||
|
secretPath: destinationSecretPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
for (const { action, subject: permissionSubject } of permissionChecks) {
|
||||||
ProjectPermissionActions.Create,
|
ForbiddenError.from(permission).throwUnlessCan(action, subject(ProjectPermissionSub.Secrets, permissionSubject));
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
}
|
||||||
environment: destinationEnvironment,
|
|
||||||
secretPath: destinationSecretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: destinationEnvironment,
|
|
||||||
secretPath: destinationSecretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const { botKey } = await projectBotService.getBotKey(project.id);
|
const { botKey } = await projectBotService.getBotKey(project.id);
|
||||||
if (!botKey) {
|
if (!botKey) {
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ export type TGetSecretsRawDTO = {
|
|||||||
expandSecretReferences?: boolean;
|
expandSecretReferences?: boolean;
|
||||||
path: string;
|
path: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
|
viewSecretValue: boolean;
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
tagSlugs?: string[];
|
tagSlugs?: string[];
|
||||||
@@ -205,6 +206,7 @@ export type TGetASecretRawDTO = {
|
|||||||
secretName: string;
|
secretName: string;
|
||||||
path: string;
|
path: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
|
viewSecretValue: boolean;
|
||||||
expandSecretReferences?: boolean;
|
expandSecretReferences?: boolean;
|
||||||
type: "shared" | "personal";
|
type: "shared" | "personal";
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import bcrypt from "bcrypt";
|
|||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -67,7 +71,7 @@ export const serviceTokenServiceFactory = ({
|
|||||||
|
|
||||||
scopes.forEach(({ environment, secretPath }) => {
|
scopes.forEach(({ environment, secretPath }) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionSecretActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { forwardRef, TextareaHTMLAttributes, useCallback, useMemo, useRef, useSt
|
|||||||
import { faCircle, faFolder, faKey } from "@fortawesome/free-solid-svg-icons";
|
import { faCircle, faFolder, faKey } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import * as Popover from "@radix-ui/react-popover";
|
import * as Popover from "@radix-ui/react-popover";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useDebounce, useToggle } from "@app/hooks";
|
import { useDebounce, useToggle } from "@app/hooks";
|
||||||
@@ -54,6 +55,7 @@ type Props = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "onChange" | "val
|
|||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
|
secretValueHidden?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ReferenceItem = {
|
type ReferenceItem = {
|
||||||
@@ -70,6 +72,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
containerClassName,
|
containerClassName,
|
||||||
secretPath: propSecretPath,
|
secretPath: propSecretPath,
|
||||||
environment: propEnvironment,
|
environment: propEnvironment,
|
||||||
|
secretValueHidden,
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
@@ -275,6 +278,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
ref={handleRef}
|
ref={handleRef}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
value={value}
|
value={value}
|
||||||
|
valueHidden={secretValueHidden}
|
||||||
onFocus={() => setIsFocused.on()}
|
onFocus={() => setIsFocused.on()}
|
||||||
onBlur={(evt) => {
|
onBlur={(evt) => {
|
||||||
// should not on blur when its mouse down selecting a item from suggestion
|
// should not on blur when its mouse down selecting a item from suggestion
|
||||||
@@ -282,7 +286,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
setIsFocused.off();
|
setIsFocused.off();
|
||||||
}}
|
}}
|
||||||
onChange={(e) => onChange?.(e.target.value)}
|
onChange={(e) => onChange?.(e.target.value)}
|
||||||
containerClassName={containerClassName}
|
containerClassName={twMerge(containerClassName)}
|
||||||
/>
|
/>
|
||||||
</Popover.Trigger>
|
</Popover.Trigger>
|
||||||
<Popover.Content
|
<Popover.Content
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
/* eslint-disable react/no-danger */
|
/* eslint-disable react/no-danger */
|
||||||
import { forwardRef, TextareaHTMLAttributes } from "react";
|
import { forwardRef, TextareaHTMLAttributes } from "react";
|
||||||
|
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
|
|
||||||
|
import { Tooltip } from "../Tooltip";
|
||||||
|
|
||||||
const REGEX = /(\${([a-zA-Z0-9-_.]+)})/g;
|
const REGEX = /(\${([a-zA-Z0-9-_.]+)})/g;
|
||||||
const replaceContentWithDot = (str: string) => {
|
const replaceContentWithDot = (str: string) => {
|
||||||
let finalStr = "";
|
let finalStr = "";
|
||||||
@@ -14,7 +18,26 @@ const replaceContentWithDot = (str: string) => {
|
|||||||
return finalStr;
|
return finalStr;
|
||||||
};
|
};
|
||||||
|
|
||||||
const syntaxHighlight = (content?: string | null, isVisible?: boolean, isImport?: boolean) => {
|
const syntaxHighlight = (
|
||||||
|
content?: string | null,
|
||||||
|
isVisible?: boolean,
|
||||||
|
isImport?: boolean,
|
||||||
|
valueHidden?: boolean
|
||||||
|
) => {
|
||||||
|
if (valueHidden && !content)
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="opacity-60">VALUE HIDDEN</span>
|
||||||
|
<div className="relative z-50">
|
||||||
|
<Tooltip
|
||||||
|
className="!relative !z-50 !break-normal !font-inter"
|
||||||
|
content="You do not have permission to read the value of this secret."
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faQuestionCircle} className="text-xs" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
if (isImport && !content) return "IMPORTED";
|
if (isImport && !content) return "IMPORTED";
|
||||||
if (content === "") return "EMPTY";
|
if (content === "") return "EMPTY";
|
||||||
if (!content) return "EMPTY";
|
if (!content) return "EMPTY";
|
||||||
@@ -50,6 +73,7 @@ type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|||||||
isImport?: boolean;
|
isImport?: boolean;
|
||||||
isReadOnly?: boolean;
|
isReadOnly?: boolean;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
valueHidden?: boolean;
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,6 +85,7 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
value,
|
value,
|
||||||
isVisible,
|
isVisible,
|
||||||
isImport,
|
isImport,
|
||||||
|
valueHidden,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
onBlur,
|
onBlur,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
@@ -81,7 +106,7 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
<pre aria-hidden className="m-0">
|
<pre aria-hidden className="m-0">
|
||||||
<code className={`inline-block w-full ${commonClassName}`}>
|
<code className={`inline-block w-full ${commonClassName}`}>
|
||||||
<span style={{ whiteSpace: "break-spaces" }}>
|
<span style={{ whiteSpace: "break-spaces" }}>
|
||||||
{syntaxHighlight(value, isVisible || isSecretFocused, isImport)}
|
{syntaxHighlight(value, isVisible || isSecretFocused, isImport, valueHidden)}
|
||||||
</span>
|
</span>
|
||||||
</code>
|
</code>
|
||||||
</pre>
|
</pre>
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ export enum ProjectPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionSecretActions {
|
||||||
|
DescribeSecret = "read",
|
||||||
|
ReadValue = "readValue",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionDynamicSecretActions {
|
export enum ProjectPermissionDynamicSecretActions {
|
||||||
ReadRootCredential = "read-root-credential",
|
ReadRootCredential = "read-root-credential",
|
||||||
CreateRootCredential = "create-root-credential",
|
CreateRootCredential = "create-root-credential",
|
||||||
@@ -138,7 +146,7 @@ export type SecretImportSubjectFields = {
|
|||||||
|
|
||||||
export type ProjectPermissionSet =
|
export type ProjectPermissionSet =
|
||||||
| [
|
| [
|
||||||
ProjectPermissionActions,
|
ProjectPermissionSecretActions,
|
||||||
(
|
(
|
||||||
| ProjectPermissionSub.Secrets
|
| ProjectPermissionSub.Secrets
|
||||||
| (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
| (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ export const useGetProjectSecretsDetails = (
|
|||||||
search = "",
|
search = "",
|
||||||
includeSecrets,
|
includeSecrets,
|
||||||
includeFolders,
|
includeFolders,
|
||||||
|
viewSecretValue,
|
||||||
includeImports,
|
includeImports,
|
||||||
includeDynamicSecrets,
|
includeDynamicSecrets,
|
||||||
tags
|
tags
|
||||||
@@ -231,6 +232,7 @@ export const useGetProjectSecretsDetails = (
|
|||||||
limit,
|
limit,
|
||||||
orderBy,
|
orderBy,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
|
viewSecretValue,
|
||||||
offset,
|
offset,
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment,
|
||||||
@@ -247,6 +249,7 @@ export const useGetProjectSecretsDetails = (
|
|||||||
limit,
|
limit,
|
||||||
orderBy,
|
orderBy,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
|
viewSecretValue,
|
||||||
offset,
|
offset,
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment,
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export type TGetDashboardProjectSecretsDetailsDTO = Omit<
|
|||||||
TGetDashboardProjectSecretsOverviewDTO,
|
TGetDashboardProjectSecretsOverviewDTO,
|
||||||
"environments"
|
"environments"
|
||||||
> & {
|
> & {
|
||||||
|
viewSecretValue: boolean;
|
||||||
environment: string;
|
environment: string;
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
tags: Record<string, boolean>;
|
tags: Record<string, boolean>;
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ export const useGetImportedSecretsAllEnvs = ({
|
|||||||
env: encSecret.environment,
|
env: encSecret.environment,
|
||||||
key: encSecret.secretKey,
|
key: encSecret.secretKey,
|
||||||
value: encSecret.secretValue,
|
value: encSecret.secretValue,
|
||||||
|
secretValueHidden: encSecret.secretValueHidden,
|
||||||
tags: encSecret.tags,
|
tags: encSecret.tags,
|
||||||
comment: encSecret.secretComment,
|
comment: encSecret.secretComment,
|
||||||
createdAt: encSecret.createdAt,
|
createdAt: encSecret.createdAt,
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export const mergePersonalSecrets = (rawSecrets: SecretV3Raw[]) => {
|
|||||||
env: el.environment,
|
env: el.environment,
|
||||||
key: el.secretKey,
|
key: el.secretKey,
|
||||||
value: el.secretValue,
|
value: el.secretValue,
|
||||||
|
secretValueHidden: el.secretValueHidden,
|
||||||
tags: el.tags || [],
|
tags: el.tags || [],
|
||||||
comment: el.secretComment || "",
|
comment: el.secretComment || "",
|
||||||
reminderRepeatDays: el.secretReminderRepeatDays,
|
reminderRepeatDays: el.secretReminderRepeatDays,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export type SecretV3RawSanitized = {
|
|||||||
version: number;
|
version: number;
|
||||||
key: string;
|
key: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
secretValueHidden: boolean;
|
||||||
comment?: string;
|
comment?: string;
|
||||||
reminderRepeatDays?: number | null;
|
reminderRepeatDays?: number | null;
|
||||||
reminderNote?: string | null;
|
reminderNote?: string | null;
|
||||||
@@ -61,6 +62,7 @@ export type SecretV3Raw = {
|
|||||||
environment: string;
|
environment: string;
|
||||||
version: number;
|
version: number;
|
||||||
type: string;
|
type: string;
|
||||||
|
secretValueHidden: boolean;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secretValue?: string;
|
secretValue?: string;
|
||||||
|
|||||||
+28
-12
@@ -35,6 +35,11 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
|
|||||||
title,
|
title,
|
||||||
isDisabled
|
isDisabled
|
||||||
}: Props<T>) => {
|
}: Props<T>) => {
|
||||||
|
if (subject === "secrets") {
|
||||||
|
console.log("secret subject");
|
||||||
|
console.log(actions);
|
||||||
|
}
|
||||||
|
|
||||||
const { control } = useFormContext<TFormSchema>();
|
const { control } = useFormContext<TFormSchema>();
|
||||||
const items = useFieldArray({
|
const items = useFieldArray({
|
||||||
control,
|
control,
|
||||||
@@ -116,25 +121,36 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
|
|||||||
<div className="w-1/4">Actions</div>
|
<div className="w-1/4">Actions</div>
|
||||||
<div className="flex flex-grow flex-wrap justify-start gap-8">
|
<div className="flex flex-grow flex-wrap justify-start gap-8">
|
||||||
{actions.map(({ label, value }) => {
|
{actions.map(({ label, value }) => {
|
||||||
|
if (subject === "secrets") {
|
||||||
|
console.log("value", value);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof value !== "string") return undefined;
|
if (typeof value !== "string") return undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
key={`${el.id}-${label}`}
|
key={`${el.id}-${label}`}
|
||||||
name={`permissions.${subject}.${rootIndex}.${value}` as any}
|
name={`permissions.${subject}.${rootIndex}.${value}` as any}
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
render={({ field }) => (
|
render={({ field }) => {
|
||||||
<div className="flex items-center justify-center">
|
if (subject === "secrets") {
|
||||||
<Checkbox
|
console.log("field", field);
|
||||||
isDisabled={isDisabled}
|
}
|
||||||
isChecked={Boolean(field.value)}
|
|
||||||
onCheckedChange={field.onChange}
|
return (
|
||||||
id={`permissions.${subject}.${rootIndex}.${String(value)}`}
|
<div className="flex items-center justify-center">
|
||||||
>
|
<Checkbox
|
||||||
{label}
|
isDisabled={isDisabled}
|
||||||
</Checkbox>
|
isChecked={Boolean(field.value)}
|
||||||
</div>
|
onCheckedChange={field.onChange}
|
||||||
)}
|
id={`permissions.${subject}.${rootIndex}.${String(value)}`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
+34
-2
@@ -9,6 +9,7 @@ import {
|
|||||||
PermissionConditionOperators,
|
PermissionConditionOperators,
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
ProjectPermissionKmipActions,
|
ProjectPermissionKmipActions,
|
||||||
|
ProjectPermissionSecretActions,
|
||||||
ProjectPermissionSecretSyncActions,
|
ProjectPermissionSecretSyncActions,
|
||||||
TPermissionCondition,
|
TPermissionCondition,
|
||||||
TPermissionConditionOperators
|
TPermissionConditionOperators
|
||||||
@@ -22,6 +23,14 @@ const GeneralPolicyActionSchema = z.object({
|
|||||||
create: z.boolean().optional()
|
create: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const SecretPolicyActionSchema = z.object({
|
||||||
|
read: z.boolean().optional(), // describe secret
|
||||||
|
edit: z.boolean().optional(),
|
||||||
|
delete: z.boolean().optional(),
|
||||||
|
create: z.boolean().optional(),
|
||||||
|
readValue: z.boolean().optional()
|
||||||
|
});
|
||||||
|
|
||||||
const CmekPolicyActionSchema = z.object({
|
const CmekPolicyActionSchema = z.object({
|
||||||
read: z.boolean().optional(),
|
read: z.boolean().optional(),
|
||||||
edit: z.boolean().optional(),
|
edit: z.boolean().optional(),
|
||||||
@@ -114,7 +123,7 @@ export const projectRoleFormSchema = z.object({
|
|||||||
.refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }),
|
.refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }),
|
||||||
permissions: z
|
permissions: z
|
||||||
.object({
|
.object({
|
||||||
[ProjectPermissionSub.Secrets]: GeneralPolicyActionSchema.extend({
|
[ProjectPermissionSub.Secrets]: SecretPolicyActionSchema.extend({
|
||||||
inverted: z.boolean().optional(),
|
inverted: z.boolean().optional(),
|
||||||
conditions: ConditionSchema
|
conditions: ConditionSchema
|
||||||
})
|
})
|
||||||
@@ -283,6 +292,28 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subject === ProjectPermissionSub.Secrets) {
|
||||||
|
const canRead = action.includes(ProjectPermissionSecretActions.DescribeSecret);
|
||||||
|
const canEdit = action.includes(ProjectPermissionSecretActions.Edit);
|
||||||
|
const canDelete = action.includes(ProjectPermissionSecretActions.Delete);
|
||||||
|
const canCreate = action.includes(ProjectPermissionSecretActions.Create);
|
||||||
|
const canReadValue = action.includes(ProjectPermissionSecretActions.ReadValue);
|
||||||
|
|
||||||
|
// from above statement we are sure it won't be undefined
|
||||||
|
formVal[subject]!.push({
|
||||||
|
read: canRead,
|
||||||
|
create: canCreate,
|
||||||
|
edit: canEdit,
|
||||||
|
delete: canDelete,
|
||||||
|
readValue: canReadValue,
|
||||||
|
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
||||||
|
inverted
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// for other subjects
|
// for other subjects
|
||||||
const canRead = action.includes(ProjectPermissionActions.Read);
|
const canRead = action.includes(ProjectPermissionActions.Read);
|
||||||
const canEdit = action.includes(ProjectPermissionActions.Edit);
|
const canEdit = action.includes(ProjectPermissionActions.Edit);
|
||||||
@@ -483,8 +514,9 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
[ProjectPermissionSub.Secrets]: {
|
[ProjectPermissionSub.Secrets]: {
|
||||||
title: "Secrets",
|
title: "Secrets",
|
||||||
actions: [
|
actions: [
|
||||||
{ label: "Read", value: "read" },
|
{ label: "Describe Secret", value: "read" },
|
||||||
{ label: "Create", value: "create" },
|
{ label: "Create", value: "create" },
|
||||||
|
{ label: "Read Value", value: "readValue" },
|
||||||
{ label: "Modify", value: "edit" },
|
{ label: "Modify", value: "edit" },
|
||||||
{ label: "Remove", value: "delete" }
|
{ label: "Remove", value: "delete" }
|
||||||
]
|
]
|
||||||
|
|||||||
+4
@@ -39,6 +39,7 @@ type Props = {
|
|||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
isImportedSecret: boolean;
|
isImportedSecret: boolean;
|
||||||
environment: string;
|
environment: string;
|
||||||
|
secretValueHidden: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
|
onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
|
||||||
onSecretUpdate: (
|
onSecretUpdate: (
|
||||||
@@ -58,6 +59,7 @@ export const SecretEditRow = ({
|
|||||||
isImportedSecret,
|
isImportedSecret,
|
||||||
onSecretUpdate,
|
onSecretUpdate,
|
||||||
secretName,
|
secretName,
|
||||||
|
secretValueHidden,
|
||||||
onSecretCreate,
|
onSecretCreate,
|
||||||
onSecretDelete,
|
onSecretDelete,
|
||||||
environment,
|
environment,
|
||||||
@@ -151,6 +153,7 @@ export const SecretEditRow = ({
|
|||||||
value={field.value as string}
|
value={field.value as string}
|
||||||
key="secret-input"
|
key="secret-input"
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
|
secretValueHidden={secretValueHidden}
|
||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
isImport={isImportedSecret}
|
isImport={isImportedSecret}
|
||||||
@@ -158,6 +161,7 @@ export const SecretEditRow = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"flex w-24 justify-center space-x-3 pl-2 transition-all",
|
"flex w-24 justify-center space-x-3 pl-2 transition-all",
|
||||||
|
|||||||
+6
-3
@@ -221,10 +221,13 @@ export const SecretOverviewTableRow = ({
|
|||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
isVisible={isSecretVisible}
|
isVisible={isSecretVisible}
|
||||||
secretName={secretKey}
|
secretName={secretKey}
|
||||||
|
secretValueHidden={secret?.secretValueHidden || false}
|
||||||
defaultValue={
|
defaultValue={
|
||||||
secret?.valueOverride ||
|
secret?.secretValueHidden
|
||||||
secret?.value ||
|
? ""
|
||||||
importedSecret?.secret?.value
|
: secret?.valueOverride ||
|
||||||
|
secret?.value ||
|
||||||
|
importedSecret?.secret?.value
|
||||||
}
|
}
|
||||||
secretId={secret?.id}
|
secretId={secret?.id}
|
||||||
isOverride={Boolean(secret?.valueOverride)}
|
isOverride={Boolean(secret?.valueOverride)}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import {
|
|||||||
useSelectedSecrets
|
useSelectedSecrets
|
||||||
} from "./SecretMainPage.store";
|
} from "./SecretMainPage.store";
|
||||||
import { Filter, RowType } from "./SecretMainPage.types";
|
import { Filter, RowType } from "./SecretMainPage.types";
|
||||||
|
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
|
||||||
const LOADER_TEXT = [
|
const LOADER_TEXT = [
|
||||||
"Retrieving your encrypted secrets...",
|
"Retrieving your encrypted secrets...",
|
||||||
@@ -103,7 +104,7 @@ const Page = () => {
|
|||||||
const projectSlug = currentWorkspace?.slug || "";
|
const projectSlug = currentWorkspace?.slug || "";
|
||||||
const secretPath = (routerQueryParams.secretPath as string) || "/";
|
const secretPath = (routerQueryParams.secretPath as string) || "/";
|
||||||
const canReadSecret = permission.can(
|
const canReadSecret = permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -111,6 +112,17 @@ const Page = () => {
|
|||||||
secretTags: ["*"]
|
secretTags: ["*"]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
const canReadSecretValue = permission.can(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: "*",
|
||||||
|
secretTags: ["*"]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Can read secret value", canReadSecret);
|
||||||
|
|
||||||
const canReadSecretImports = permission.can(
|
const canReadSecretImports = permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
@@ -176,6 +188,7 @@ const Page = () => {
|
|||||||
orderDirection,
|
orderDirection,
|
||||||
includeImports: canReadSecretImports && filter.include.import,
|
includeImports: canReadSecretImports && filter.include.import,
|
||||||
includeFolders: filter.include.folder,
|
includeFolders: filter.include.folder,
|
||||||
|
viewSecretValue: canReadSecretValue,
|
||||||
includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic,
|
includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic,
|
||||||
includeSecrets: canReadSecret && filter.include.secret,
|
includeSecrets: canReadSecret && filter.include.secret,
|
||||||
tags: filter.tags
|
tags: filter.tags
|
||||||
|
|||||||
+28
-4
@@ -52,6 +52,8 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
|
|||||||
|
|
||||||
import { CreateReminderForm } from "./CreateReminderForm";
|
import { CreateReminderForm } from "./CreateReminderForm";
|
||||||
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
|
||||||
|
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
@@ -122,7 +124,7 @@ export const SecretDetailSidebar = ({
|
|||||||
const selectTagSlugs = selectedTags.map((i) => i.slug);
|
const selectTagSlugs = selectedTags.map((i) => i.slug);
|
||||||
|
|
||||||
const cannotEditSecret = permission.cannot(
|
const cannotEditSecret = permission.cannot(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -130,16 +132,31 @@ export const SecretDetailSidebar = ({
|
|||||||
secretTags: selectTagSlugs
|
secretTags: selectTagSlugs
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cannotReadSecretValue = permission.cannot(
|
||||||
|
ProjectPermissionSecretActions.ReadValue,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: secretKey,
|
||||||
|
secretTags: selectTagSlugs
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const isReadOnly =
|
const isReadOnly =
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: secretKey,
|
secretName: secretKey,
|
||||||
secretTags: selectTagSlugs
|
secretTags: selectTagSlugs
|
||||||
})
|
})
|
||||||
) && cannotEditSecret;
|
) &&
|
||||||
|
cannotEditSecret &&
|
||||||
|
cannotReadSecretValue;
|
||||||
|
|
||||||
|
console.log("cannotReadSecretValue", cannotReadSecretValue);
|
||||||
|
|
||||||
const overrideAction = watch("overrideAction");
|
const overrideAction = watch("overrideAction");
|
||||||
const isOverridden =
|
const isOverridden =
|
||||||
@@ -261,7 +278,14 @@ export const SecretDetailSidebar = ({
|
|||||||
key="secret-value"
|
key="secret-value"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl label="Value">
|
<FormControl
|
||||||
|
helperText={
|
||||||
|
cannotReadSecretValue
|
||||||
|
? "The value of this secret is hidden because you don't have the read secret value permission."
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
label="Value"
|
||||||
|
>
|
||||||
<InfisicalSecretInput
|
<InfisicalSecretInput
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
|
|||||||
+17
-4
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
/* eslint-disable simple-import-sort/imports */
|
/* eslint-disable simple-import-sort/imports */
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
@@ -44,6 +45,7 @@ import {
|
|||||||
SecretReferenceTree
|
SecretReferenceTree
|
||||||
} from "@app/components/secrets/SecretReferenceDetails";
|
} from "@app/components/secrets/SecretReferenceDetails";
|
||||||
|
|
||||||
|
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
import {
|
import {
|
||||||
FontAwesomeSpriteName,
|
FontAwesomeSpriteName,
|
||||||
formSchema,
|
formSchema,
|
||||||
@@ -99,8 +101,14 @@ export const SecretItem = memo(
|
|||||||
trigger,
|
trigger,
|
||||||
formState: { isDirty, isSubmitting, errors }
|
formState: { isDirty, isSubmitting, errors }
|
||||||
} = useForm<TFormSchema>({
|
} = useForm<TFormSchema>({
|
||||||
defaultValues: secret,
|
defaultValues: {
|
||||||
values: secret,
|
...secret,
|
||||||
|
value: secret.secretValueHidden ? "" : secret.value
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
...secret,
|
||||||
|
value: secret.secretValueHidden ? "" : secret.value
|
||||||
|
},
|
||||||
resolver: zodResolver(formSchema)
|
resolver: zodResolver(formSchema)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -123,7 +131,7 @@ export const SecretItem = memo(
|
|||||||
|
|
||||||
const isReadOnly =
|
const isReadOnly =
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionSecretActions.DescribeSecret,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -132,7 +140,7 @@ export const SecretItem = memo(
|
|||||||
})
|
})
|
||||||
) &&
|
) &&
|
||||||
permission.cannot(
|
permission.cannot(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionSecretActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -141,6 +149,9 @@ export const SecretItem = memo(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { secretValueHidden } = secret;
|
||||||
|
console.log(`Secret Key: ${secret.key}, hidden: ${secretValueHidden}`);
|
||||||
|
|
||||||
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
|
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timer: NodeJS.Timeout;
|
let timer: NodeJS.Timeout;
|
||||||
@@ -279,12 +290,14 @@ export const SecretItem = memo(
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<InfisicalSecretInput
|
<InfisicalSecretInput
|
||||||
|
secretValueHidden={secretValueHidden}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
key="secret-value"
|
key="secret-value"
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
environment={environment}
|
environment={environment}
|
||||||
secretPath={secretPath}
|
secretPath={secretPath}
|
||||||
{...field}
|
{...field}
|
||||||
|
defaultValue={secretValueHidden ? "" : undefined}
|
||||||
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user