mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 00:26:40 +00:00
feat: update dynamic secret permissioning
This commit is contained in:
@@ -31,7 +31,7 @@ import {
|
|||||||
|
|
||||||
type TDynamicSecretLeaseServiceFactoryDep = {
|
type TDynamicSecretLeaseServiceFactoryDep = {
|
||||||
dynamicSecretLeaseDAL: TDynamicSecretLeaseDALFactory;
|
dynamicSecretLeaseDAL: TDynamicSecretLeaseDALFactory;
|
||||||
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findOne">;
|
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findOne" | "findOneWithMetadata">;
|
||||||
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||||
dynamicSecretQueueService: TDynamicSecretLeaseQueueServiceFactory;
|
dynamicSecretQueueService: TDynamicSecretLeaseQueueServiceFactory;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
@@ -78,10 +78,6 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
if (!plan?.dynamicSecret) {
|
if (!plan?.dynamicSecret) {
|
||||||
@@ -96,12 +92,21 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
|
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
|
||||||
});
|
});
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOneWithMetadata({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg)
|
if (!dynamicSecretCfg)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
|
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecretCfg.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const totalLeasesTaken = await dynamicSecretLeaseDAL.countLeasesForDynamicSecret(dynamicSecretCfg.id);
|
const totalLeasesTaken = await dynamicSecretLeaseDAL.countLeasesForDynamicSecret(dynamicSecretCfg.id);
|
||||||
if (totalLeasesTaken >= appCfg.MAX_LEASE_LIMIT)
|
if (totalLeasesTaken >= appCfg.MAX_LEASE_LIMIT)
|
||||||
throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` });
|
throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` });
|
||||||
@@ -159,10 +164,6 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
@@ -187,7 +188,20 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
|
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
const dynamicSecretCfg = await dynamicSecretDAL.findOneWithMetadata({
|
||||||
|
id: dynamicSecretLease.dynamicSecretId,
|
||||||
|
folderId: folder.id
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecretCfg.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString()
|
secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString()
|
||||||
@@ -239,10 +253,6 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
@@ -259,7 +269,20 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!dynamicSecretLease || dynamicSecretLease.dynamicSecret.folderId !== folder.id)
|
if (!dynamicSecretLease || dynamicSecretLease.dynamicSecret.folderId !== folder.id)
|
||||||
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
|
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
|
||||||
|
|
||||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
const dynamicSecretCfg = await dynamicSecretDAL.findOneWithMetadata({
|
||||||
|
id: dynamicSecretLease.dynamicSecretId,
|
||||||
|
folderId: folder.id
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecretCfg.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString()
|
secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString()
|
||||||
@@ -309,10 +332,6 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -320,12 +339,21 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
|
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
|
||||||
});
|
});
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOneWithMetadata({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg)
|
if (!dynamicSecretCfg)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
|
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecretCfg.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||||
return dynamicSecretLeases;
|
return dynamicSecretLeases;
|
||||||
};
|
};
|
||||||
@@ -352,10 +380,6 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: `Folder with path '${path}' not found` });
|
if (!folder) throw new NotFoundError({ message: `Folder with path '${path}' not found` });
|
||||||
@@ -364,6 +388,20 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!dynamicSecretLease)
|
if (!dynamicSecretLease)
|
||||||
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
|
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOneWithMetadata({
|
||||||
|
id: dynamicSecretLease.dynamicSecretId,
|
||||||
|
folderId: folder.id
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecretCfg.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return dynamicSecretLease;
|
return dynamicSecretLease;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName, TDynamicSecrets } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
import {
|
||||||
|
buildFindFilter,
|
||||||
|
ormify,
|
||||||
|
prependTableNameToFindFilter,
|
||||||
|
selectAllTableCols,
|
||||||
|
sqlNestRelationships,
|
||||||
|
TFindFilter,
|
||||||
|
TFindOpt
|
||||||
|
} from "@app/lib/knex";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||||
|
|
||||||
@@ -12,7 +20,7 @@ export type TDynamicSecretDALFactory = ReturnType<typeof dynamicSecretDALFactory
|
|||||||
export const dynamicSecretDALFactory = (db: TDbClient) => {
|
export const dynamicSecretDALFactory = (db: TDbClient) => {
|
||||||
const orm = ormify(db, TableName.DynamicSecret);
|
const orm = ormify(db, TableName.DynamicSecret);
|
||||||
|
|
||||||
const findOneWithMetadata = async (filter: { name?: string; folderId?: string }, tx?: Knex) => {
|
const findOneWithMetadata = async (filter: TFindFilter<TDynamicSecrets>, tx?: Knex) => {
|
||||||
const query = (tx || db.replicaNode())(TableName.DynamicSecret)
|
const query = (tx || db.replicaNode())(TableName.DynamicSecret)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.ResourceMetadata,
|
TableName.ResourceMetadata,
|
||||||
@@ -25,7 +33,7 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
|
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
|
||||||
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
|
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
|
||||||
)
|
)
|
||||||
.where(filter);
|
.where(prependTableNameToFindFilter(TableName.DynamicSecret, filter));
|
||||||
|
|
||||||
const docs = sqlNestRelationships({
|
const docs = sqlNestRelationships({
|
||||||
data: await query,
|
data: await query,
|
||||||
@@ -47,6 +55,51 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
|
|||||||
return docs[0];
|
return docs[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findWithMetadata = async (
|
||||||
|
filter: TFindFilter<TDynamicSecrets>,
|
||||||
|
{ offset, limit, sort, tx }: TFindOpt<TDynamicSecrets> = {}
|
||||||
|
) => {
|
||||||
|
const query = (tx || db.replicaNode())(TableName.DynamicSecret)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ResourceMetadata,
|
||||||
|
`${TableName.ResourceMetadata}.dynamicSecretId`,
|
||||||
|
`${TableName.DynamicSecret}.id`
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.DynamicSecret))
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
|
||||||
|
)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
.where(buildFindFilter(filter));
|
||||||
|
|
||||||
|
if (limit) void query.limit(limit);
|
||||||
|
if (offset) void query.offset(offset);
|
||||||
|
if (sort) {
|
||||||
|
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = sqlNestRelationships({
|
||||||
|
data: await query,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => el,
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "metadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return docs;
|
||||||
|
};
|
||||||
|
|
||||||
// find dynamic secrets for multiple environments (folder IDs are cross env, thus need to rank for pagination)
|
// find dynamic secrets for multiple environments (folder IDs are cross env, thus need to rank for pagination)
|
||||||
const listDynamicSecretsByFolderIds = async (
|
const listDynamicSecretsByFolderIds = async (
|
||||||
{
|
{
|
||||||
@@ -74,18 +127,27 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
|
|||||||
void bd.whereILike(`${TableName.DynamicSecret}.name`, `%${search}%`);
|
void bd.whereILike(`${TableName.DynamicSecret}.name`, `%${search}%`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ResourceMetadata,
|
||||||
|
`${TableName.ResourceMetadata}.dynamicSecretId`,
|
||||||
|
`${TableName.DynamicSecret}.id`
|
||||||
|
)
|
||||||
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`)
|
.leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`)
|
||||||
.leftJoin(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
.leftJoin(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||||
.select(
|
.select(
|
||||||
selectAllTableCols(TableName.DynamicSecret),
|
selectAllTableCols(TableName.DynamicSecret),
|
||||||
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
db.raw(`DENSE_RANK() OVER (ORDER BY ${TableName.DynamicSecret}."name" ${orderDirection}) as rank`)
|
db.raw(`DENSE_RANK() OVER (ORDER BY ${TableName.DynamicSecret}."name" ${orderDirection}) as rank`),
|
||||||
|
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
|
||||||
)
|
)
|
||||||
.orderBy(`${TableName.DynamicSecret}.${orderBy}`, orderDirection);
|
.orderBy(`${TableName.DynamicSecret}.${orderBy}`, orderDirection);
|
||||||
|
|
||||||
|
let queryWithLimit;
|
||||||
if (limit) {
|
if (limit) {
|
||||||
const rankOffset = offset + 1;
|
const rankOffset = offset + 1;
|
||||||
return await (tx || db)
|
queryWithLimit = (tx || db.replicaNode())
|
||||||
.with("w", query)
|
.with("w", query)
|
||||||
.select("*")
|
.select("*")
|
||||||
.from<Awaited<typeof query>[number]>("w")
|
.from<Awaited<typeof query>[number]>("w")
|
||||||
@@ -93,7 +155,22 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
|
|||||||
.andWhere("w.rank", "<", rankOffset + limit);
|
.andWhere("w.rank", "<", rankOffset + limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dynamicSecrets = await query;
|
const dynamicSecrets = sqlNestRelationships({
|
||||||
|
data: await (queryWithLimit || query),
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => el,
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "metadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
return dynamicSecrets;
|
return dynamicSecrets;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -101,5 +178,5 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...orm, listDynamicSecretsByFolderIds, findOneWithMetadata };
|
return { ...orm, listDynamicSecretsByFolderIds, findOneWithMetadata, findWithMetadata };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,9 +91,10 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path, metadata })
|
||||||
);
|
);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@@ -196,10 +197,6 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
if (!plan?.dynamicSecret) {
|
if (!plan?.dynamicSecret) {
|
||||||
@@ -212,12 +209,22 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOneWithMetadata({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg) {
|
if (!dynamicSecretCfg) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found`
|
message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecretCfg.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (newName) {
|
if (newName) {
|
||||||
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name: newName, folderId: folder.id });
|
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name: newName, folderId: folder.id });
|
||||||
if (existingDynamicSecret)
|
if (existingDynamicSecret)
|
||||||
@@ -315,20 +322,25 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOneWithMetadata({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg) {
|
if (!dynamicSecretCfg) {
|
||||||
throw new NotFoundError({ message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found` });
|
throw new NotFoundError({ message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecretCfg.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const leases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
const leases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||||
// when not forced we check with the external system to first remove the things
|
// when not forced we check with the external system to first remove the things
|
||||||
// we introduce a forced concept because consider the external lease got deleted by some other external like a human or another system
|
// we introduce a forced concept because consider the external lease got deleted by some other external like a human or another system
|
||||||
@@ -376,14 +388,6 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -394,6 +398,24 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` });
|
throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecretCfg.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecretCfg.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId
|
projectId
|
||||||
@@ -475,7 +497,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
ProjectPermissionSub.DynamicSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
@@ -522,16 +544,12 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.find(
|
const dynamicSecretCfg = await dynamicSecretDAL.findWithMetadata(
|
||||||
{ folderId: folder.id, $search: search ? { name: `%${search}%` } : undefined },
|
{ folderId: folder.id, $search: search ? { name: `%${search}%` } : undefined },
|
||||||
{
|
{
|
||||||
limit,
|
limit,
|
||||||
@@ -539,7 +557,17 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
sort: orderBy ? [[orderBy, orderDirection]] : undefined
|
sort: orderBy ? [[orderBy, orderDirection]] : undefined
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return dynamicSecretCfg;
|
|
||||||
|
return dynamicSecretCfg.filter((dynamicSecret) => {
|
||||||
|
return permission.can(
|
||||||
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: environmentSlug,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecret.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const listDynamicSecretsByFolderIds = async (
|
const listDynamicSecretsByFolderIds = async (
|
||||||
@@ -591,24 +619,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
isInternal,
|
isInternal,
|
||||||
...params
|
...params
|
||||||
}: TListDynamicSecretsMultiEnvDTO) => {
|
}: TListDynamicSecretsMultiEnvDTO) => {
|
||||||
if (!isInternal) {
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
actor,
|
||||||
actor,
|
actorId,
|
||||||
actorId,
|
projectId,
|
||||||
projectId,
|
actorAuthMethod,
|
||||||
actorAuthMethod,
|
actorOrgId,
|
||||||
actorOrgId,
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// verify user has access to each env in request
|
|
||||||
environmentSlugs.forEach((environmentSlug) =>
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
||||||
if (!folders.length)
|
if (!folders.length)
|
||||||
@@ -621,7 +639,16 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
...params
|
...params
|
||||||
});
|
});
|
||||||
|
|
||||||
return dynamicSecretCfg;
|
return dynamicSecretCfg.filter((dynamicSecret) => {
|
||||||
|
return permission.can(
|
||||||
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment: dynamicSecret.environment,
|
||||||
|
secretPath: path,
|
||||||
|
metadata: dynamicSecret.metadata
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchAzureEntraIdUsers = async ({
|
const fetchAzureEntraIdUsers = async ({
|
||||||
|
|||||||
@@ -144,6 +144,10 @@ export type SecretFolderSubjectFields = {
|
|||||||
export type DynamicSecretSubjectFields = {
|
export type DynamicSecretSubjectFields = {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
|
metadata?: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecretImportSubjectFields = {
|
export type SecretImportSubjectFields = {
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ActionProjectType, SecretFoldersSchema, SecretImportsSchema } from "@app/db/schemas";
|
import { SecretFoldersSchema, SecretImportsSchema } from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import {
|
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
||||||
ProjectPermissionDynamicSecretActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
|
||||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||||
import { DASHBOARD } from "@app/lib/api-docs";
|
import { DASHBOARD } from "@app/lib/api-docs";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
@@ -289,24 +285,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
totalCount: totalFolderCount ?? 0
|
totalCount: totalFolderCount ?? 0
|
||||||
};
|
};
|
||||||
|
|
||||||
const { permission } = await server.services.permission.getProjectPermission({
|
if (includeDynamicSecrets) {
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
|
||||||
});
|
|
||||||
|
|
||||||
const allowedDynamicSecretEnvironments = // filter envs user has access to
|
|
||||||
environments.filter((environment) =>
|
|
||||||
permission.can(
|
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (includeDynamicSecrets && allowedDynamicSecretEnvironments.length) {
|
|
||||||
// this is the unique count, ie duplicate secrets across envs only count as 1
|
// this is the unique count, ie duplicate secrets across envs only count as 1
|
||||||
totalDynamicSecretCount = await server.services.dynamicSecret.getCountMultiEnv({
|
totalDynamicSecretCount = await server.services.dynamicSecret.getCountMultiEnv({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@@ -315,7 +294,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId,
|
projectId,
|
||||||
search,
|
search,
|
||||||
environmentSlugs: allowedDynamicSecretEnvironments,
|
environmentSlugs: environments,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
isInternal: true
|
isInternal: true
|
||||||
});
|
});
|
||||||
@@ -330,7 +309,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
search,
|
search,
|
||||||
orderBy,
|
orderBy,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
environmentSlugs: allowedDynamicSecretEnvironments,
|
environmentSlugs: environments,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
limit: remainingLimit,
|
limit: remainingLimit,
|
||||||
offset: adjustedOffset,
|
offset: adjustedOffset,
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ export type SecretFolderSubjectFields = {
|
|||||||
export type DynamicSecretSubjectFields = {
|
export type DynamicSecretSubjectFields = {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
|
metadata: (string | { key: string; value: string })[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecretImportSubjectFields = {
|
export type SecretImportSubjectFields = {
|
||||||
|
|||||||
@@ -207,7 +207,8 @@ export const OverviewPage = () => {
|
|||||||
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, {
|
subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
environment: env.slug,
|
environment: env.slug,
|
||||||
secretPath
|
secretPath,
|
||||||
|
metadata: ["*"]
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ const Page = () => {
|
|||||||
|
|
||||||
const canReadDynamicSecret = permission.can(
|
const canReadDynamicSecret = permission.can(
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath, metadata: ["*"] })
|
||||||
);
|
);
|
||||||
|
|
||||||
const canReadSecretRotations = permission.can(
|
const canReadSecretRotations = permission.can(
|
||||||
|
|||||||
+2
-1
@@ -854,7 +854,8 @@ export const ActionBar = ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
secretName: "*",
|
secretName: "*",
|
||||||
secretTags: ["*"]
|
secretTags: ["*"],
|
||||||
|
metadata: ["*"]
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
|
|||||||
+18
-5
@@ -29,11 +29,13 @@ import {
|
|||||||
import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useGetDynamicSecretLeases, useRevokeDynamicSecretLease } from "@app/hooks/api";
|
import { useGetDynamicSecretLeases, useRevokeDynamicSecretLease } from "@app/hooks/api";
|
||||||
|
import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
|
||||||
import { DynamicSecretLeaseStatus } from "@app/hooks/api/dynamicSecretLease/types";
|
import { DynamicSecretLeaseStatus } from "@app/hooks/api/dynamicSecretLease/types";
|
||||||
|
|
||||||
import { RenewDynamicSecretLease } from "./RenewDynamicSecretLease";
|
import { RenewDynamicSecretLease } from "./RenewDynamicSecretLease";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
dynamicSecret: TDynamicSecret;
|
||||||
dynamicSecretName: string;
|
dynamicSecretName: string;
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
@@ -48,7 +50,8 @@ export const DynamicSecretLease = ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
onClickNewLease,
|
onClickNewLease,
|
||||||
onClose
|
onClose,
|
||||||
|
dynamicSecret
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { handlePopUpOpen, popUp, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { handlePopUpOpen, popUp, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"deleteSecret",
|
"deleteSecret",
|
||||||
@@ -140,7 +143,11 @@ export const DynamicSecretLease = ({
|
|||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
metadata: dynamicSecret.metadata
|
||||||
|
})}
|
||||||
renderTooltip
|
renderTooltip
|
||||||
allowedLabel="Renew"
|
allowedLabel="Renew"
|
||||||
>
|
>
|
||||||
@@ -159,7 +166,11 @@ export const DynamicSecretLease = ({
|
|||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
metadata: dynamicSecret.metadata
|
||||||
|
})}
|
||||||
renderTooltip
|
renderTooltip
|
||||||
allowedLabel="Delete"
|
allowedLabel="Delete"
|
||||||
>
|
>
|
||||||
@@ -181,7 +192,8 @@ export const DynamicSecretLease = ({
|
|||||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||||
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath,
|
||||||
|
metadata: dynamicSecret.metadata
|
||||||
})}
|
})}
|
||||||
renderTooltip
|
renderTooltip
|
||||||
allowedLabel="Force Delete. This action will remove the secret from internal storage, but it will remain in external systems."
|
allowedLabel="Force Delete. This action will remove the secret from internal storage, but it will remain in external systems."
|
||||||
@@ -215,7 +227,8 @@ export const DynamicSecretLease = ({
|
|||||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||||
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath,
|
||||||
|
metadata: dynamicSecret.metadata
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
|
|||||||
+16
-3
@@ -144,7 +144,11 @@ export const DynamicSecretListView = ({
|
|||||||
<div className="flex items-center space-x-2 px-4 py-2">
|
<div className="flex items-center space-x-2 px-4 py-2">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionDynamicSecretActions.Lease}
|
I={ProjectPermissionDynamicSecretActions.Lease}
|
||||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
metadata: secret.metadata
|
||||||
|
})}
|
||||||
renderTooltip
|
renderTooltip
|
||||||
allowedLabel="Edit"
|
allowedLabel="Edit"
|
||||||
>
|
>
|
||||||
@@ -186,7 +190,11 @@ export const DynamicSecretListView = ({
|
|||||||
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
|
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionDynamicSecretActions.EditRootCredential}
|
I={ProjectPermissionDynamicSecretActions.EditRootCredential}
|
||||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
metadata: secret.metadata
|
||||||
|
})}
|
||||||
renderTooltip
|
renderTooltip
|
||||||
allowedLabel="Edit"
|
allowedLabel="Edit"
|
||||||
>
|
>
|
||||||
@@ -208,7 +216,11 @@ export const DynamicSecretListView = ({
|
|||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionDynamicSecretActions.DeleteRootCredential}
|
I={ProjectPermissionDynamicSecretActions.DeleteRootCredential}
|
||||||
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
|
a={subject(ProjectPermissionSub.DynamicSecrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
metadata: secret.metadata
|
||||||
|
})}
|
||||||
renderTooltip
|
renderTooltip
|
||||||
allowedLabel="Delete"
|
allowedLabel="Delete"
|
||||||
>
|
>
|
||||||
@@ -236,6 +248,7 @@ export const DynamicSecretListView = ({
|
|||||||
className="max-w-3xl"
|
className="max-w-3xl"
|
||||||
>
|
>
|
||||||
<DynamicSecretLease
|
<DynamicSecretLease
|
||||||
|
dynamicSecret={secret}
|
||||||
onClickNewLease={() => handlePopUpOpen("createDynamicSecretLease", secret)}
|
onClickNewLease={() => handlePopUpOpen("createDynamicSecretLease", secret)}
|
||||||
onClose={() => handlePopUpClose("dynamicSecretLeases")}
|
onClose={() => handlePopUpClose("dynamicSecretLeases")}
|
||||||
projectSlug={projectSlug}
|
projectSlug={projectSlug}
|
||||||
|
|||||||
Reference in New Issue
Block a user