mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-20 07:21:49 +00:00
Merge pull request #3677 from Infisical/secret-scanning-v2-pt-1
feature(secret-scanning-v2): secret scanning architecture and github data source
This commit is contained in:
+1
-1
@@ -87,7 +87,7 @@ export const GroupsSection = () => {
|
||||
<GroupTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteGroup.isOpen}
|
||||
title={`Are you sure want to remove the group ${
|
||||
title={`Are you sure you want to remove the group ${
|
||||
(popUp?.deleteGroup?.data as { name: string })?.name || ""
|
||||
} from the project?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
|
||||
|
||||
@@ -432,7 +432,7 @@ export const IdentityTab = withProjectPermission(
|
||||
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteIdentity.isOpen}
|
||||
title={`Are you sure want to remove ${
|
||||
title={`Are you sure you want to remove ${
|
||||
(popUp?.deleteIdentity?.data as { name: string })?.name || ""
|
||||
} from the project?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
|
||||
|
||||
+1
-1
@@ -195,7 +195,7 @@ export const ProjectRoleList = () => {
|
||||
<RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteRole.isOpen}
|
||||
title={`Are you sure want to delete ${
|
||||
title={`Are you sure you want to delete ${
|
||||
(popUp?.deleteRole?.data as TProjectRole)?.name || " "
|
||||
} role?`}
|
||||
deleteKey={(popUp?.deleteRole?.data as TProjectRole)?.slug || ""}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||
|
||||
import { AccessControlPage } from "./AccessControlPage";
|
||||
|
||||
const AccessControlPageQuerySchema = z.object({
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member),
|
||||
requesterEmail: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/access-management"
|
||||
)({
|
||||
component: AccessControlPage,
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||
search: {
|
||||
middlewares: [stripSearchParams({ requesterEmail: "" })]
|
||||
},
|
||||
beforeLoad: ({ context, params }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
{
|
||||
label: "Access Control",
|
||||
link: linkOptions({
|
||||
to: "/secret-scanning/$projectId/access-management",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
})
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -184,7 +184,7 @@ const Page = () => {
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteIdentity.isOpen}
|
||||
title={`Are you sure want to remove ${identityMembershipDetails?.identity?.name} from the project?`}
|
||||
title={`Are you sure you want to remove ${identityMembershipDetails?.identity?.name} from the project?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
|
||||
deleteKey="remove"
|
||||
onDeleteApproved={() => onRemoveIdentitySubmit()}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId"
|
||||
)({
|
||||
component: IdentityDetailsByIDPage,
|
||||
beforeLoad: ({ context, params }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
{
|
||||
label: "Access Control",
|
||||
link: linkOptions({
|
||||
to: "/secret-scanning/$projectId/access-management",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Identities"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
useGetWorkspaceUserDetails
|
||||
} from "@app/hooks/api";
|
||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { MemberProjectAdditionalPrivilegeSection } from "./components/MemberProjectAdditionalPrivilegeSection";
|
||||
import { MemberRoleDetailsSection } from "./components/MemberRoleDetailsSection";
|
||||
@@ -70,7 +71,21 @@ export const Page = () => {
|
||||
type: "success",
|
||||
text: "User privilege assumption has started"
|
||||
});
|
||||
window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/overview`;
|
||||
|
||||
let page: string;
|
||||
|
||||
switch (currentWorkspace.type) {
|
||||
case ProjectType.SecretScanning:
|
||||
page = "data-sources";
|
||||
break;
|
||||
case ProjectType.CertificateManager:
|
||||
page = "subscribers";
|
||||
break;
|
||||
default:
|
||||
page = "overview";
|
||||
}
|
||||
|
||||
window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/${page}`;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/members/$membershipId"
|
||||
)({
|
||||
component: MemberDetailsByIDPage,
|
||||
beforeLoad: ({ context, params }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
{
|
||||
label: "Access Control",
|
||||
link: linkOptions({
|
||||
to: "/secret-scanning/$projectId/access-management",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Users"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -170,7 +170,7 @@ const Page = () => {
|
||||
<RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteRole.isOpen}
|
||||
title={`Are you sure want to delete the project role ${data?.name ?? ""}?`}
|
||||
title={`Are you sure you want to delete the project role ${data?.name ?? ""}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteRole", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() => onDeleteRoleSubmit()}
|
||||
|
||||
+227
-4
@@ -21,6 +21,9 @@ import {
|
||||
ProjectPermissionPkiTemplateActions,
|
||||
ProjectPermissionSecretActions,
|
||||
ProjectPermissionSecretRotationActions,
|
||||
ProjectPermissionSecretScanningConfigActions,
|
||||
ProjectPermissionSecretScanningDataSourceActions,
|
||||
ProjectPermissionSecretScanningFindingActions,
|
||||
ProjectPermissionSecretSyncActions,
|
||||
ProjectPermissionSshHostActions,
|
||||
TPermissionCondition,
|
||||
@@ -98,6 +101,26 @@ const SecretRotationPolicyActionSchema = z.object({
|
||||
[ProjectPermissionSecretRotationActions.RotateSecrets]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const SecretScanningDataSourcePolicyActionSchema = z.object({
|
||||
[ProjectPermissionSecretScanningDataSourceActions.Read]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretScanningDataSourceActions.Create]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretScanningDataSourceActions.Edit]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretScanningDataSourceActions.Delete]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretScanningDataSourceActions.ReadScans]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretScanningDataSourceActions.ReadResources]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretScanningDataSourceActions.TriggerScans]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const SecretScanningFindingPolicyActionSchema = z.object({
|
||||
[ProjectPermissionSecretScanningFindingActions.Read]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretScanningFindingActions.Update]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const SecretScanningConfigPolicyActionSchema = z.object({
|
||||
[ProjectPermissionSecretScanningConfigActions.Read]: z.boolean().optional(),
|
||||
[ProjectPermissionSecretScanningConfigActions.Update]: z.boolean().optional()
|
||||
});
|
||||
|
||||
const KmipPolicyActionSchema = z.object({
|
||||
[ProjectPermissionKmipActions.ReadClients]: z.boolean().optional(),
|
||||
[ProjectPermissionKmipActions.CreateClients]: z.boolean().optional(),
|
||||
@@ -296,7 +319,13 @@ export const projectRoleFormSchema = z.object({
|
||||
[ProjectPermissionSub.Kms]: GeneralPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Cmek]: CmekPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.SecretSyncs]: SecretSyncPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.Kmip]: KmipPolicyActionSchema.array().default([])
|
||||
[ProjectPermissionSub.Kmip]: KmipPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.SecretScanningDataSources]:
|
||||
SecretScanningDataSourcePolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.SecretScanningFindings]:
|
||||
SecretScanningFindingPolicyActionSchema.array().default([]),
|
||||
[ProjectPermissionSub.SecretScanningConfigs]:
|
||||
SecretScanningConfigPolicyActionSchema.array().default([])
|
||||
})
|
||||
.partial()
|
||||
.optional()
|
||||
@@ -746,6 +775,71 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
formVal[subject]![0][ProjectPermissionSecretSyncActions.ImportSecrets] = true;
|
||||
if (canRemoveSecrets)
|
||||
formVal[subject]![0][ProjectPermissionSecretSyncActions.RemoveSecrets] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.SecretScanningDataSources) {
|
||||
const canRead = action.includes(ProjectPermissionSecretScanningDataSourceActions.Read);
|
||||
const canEdit = action.includes(ProjectPermissionSecretScanningDataSourceActions.Edit);
|
||||
const canDelete = action.includes(ProjectPermissionSecretScanningDataSourceActions.Delete);
|
||||
const canCreate = action.includes(ProjectPermissionSecretScanningDataSourceActions.Create);
|
||||
const canReadScans = action.includes(
|
||||
ProjectPermissionSecretScanningDataSourceActions.ReadScans
|
||||
);
|
||||
const canReadResources = action.includes(
|
||||
ProjectPermissionSecretScanningDataSourceActions.ReadResources
|
||||
);
|
||||
const canTriggerScans = action.includes(
|
||||
ProjectPermissionSecretScanningDataSourceActions.TriggerScans
|
||||
);
|
||||
|
||||
if (!formVal[subject]) formVal[subject] = [{}];
|
||||
|
||||
// from above statement we are sure it won't be undefined
|
||||
if (canRead)
|
||||
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Read] = true;
|
||||
if (canEdit)
|
||||
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Edit] = true;
|
||||
if (canCreate)
|
||||
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Create] = true;
|
||||
if (canDelete)
|
||||
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Delete] = true;
|
||||
if (canReadScans)
|
||||
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.ReadScans] = true;
|
||||
if (canReadResources)
|
||||
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.ReadResources] = true;
|
||||
if (canTriggerScans)
|
||||
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.TriggerScans] = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.SecretScanningFindings) {
|
||||
const canRead = action.includes(ProjectPermissionSecretScanningFindingActions.Read);
|
||||
const canUpdate = action.includes(ProjectPermissionSecretScanningFindingActions.Update);
|
||||
|
||||
if (!formVal[subject]) formVal[subject] = [{}];
|
||||
|
||||
// from above statement we are sure it won't be undefined
|
||||
if (canRead) formVal[subject]![0][ProjectPermissionSecretScanningFindingActions.Read] = true;
|
||||
if (canUpdate)
|
||||
formVal[subject]![0][ProjectPermissionSecretScanningFindingActions.Update] = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.SecretScanningConfigs) {
|
||||
const canRead = action.includes(ProjectPermissionSecretScanningConfigActions.Read);
|
||||
const canUpdate = action.includes(ProjectPermissionSecretScanningConfigActions.Update);
|
||||
|
||||
if (!formVal[subject]) formVal[subject] = [{}];
|
||||
|
||||
// from above statement we are sure it won't be undefined
|
||||
if (canRead) formVal[subject]![0][ProjectPermissionSecretScanningConfigActions.Read] = true;
|
||||
if (canUpdate)
|
||||
formVal[subject]![0][ProjectPermissionSecretScanningConfigActions.Update] = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.SshHosts) {
|
||||
@@ -770,6 +864,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
|
||||
inverted
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (subject === ProjectPermissionSub.PkiSubscribers) {
|
||||
@@ -1318,6 +1414,65 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
||||
value: ProjectPermissionKmipActions.GenerateClientCertificates
|
||||
}
|
||||
]
|
||||
},
|
||||
[ProjectPermissionSub.SecretScanningDataSources]: {
|
||||
title: "Secret Scanning Data Sources",
|
||||
actions: [
|
||||
{
|
||||
label: "Read Data Sources",
|
||||
value: ProjectPermissionSecretScanningDataSourceActions.Read
|
||||
},
|
||||
{
|
||||
label: "Create Data Sources",
|
||||
value: ProjectPermissionSecretScanningDataSourceActions.Create
|
||||
},
|
||||
{
|
||||
label: "Modify Data Sources",
|
||||
value: ProjectPermissionSecretScanningDataSourceActions.Edit
|
||||
},
|
||||
{
|
||||
label: "Delete Data Sources",
|
||||
value: ProjectPermissionSecretScanningDataSourceActions.Delete
|
||||
},
|
||||
{
|
||||
label: "Read Resources",
|
||||
value: ProjectPermissionSecretScanningDataSourceActions.ReadResources
|
||||
},
|
||||
{
|
||||
label: "Read Scans",
|
||||
value: ProjectPermissionSecretScanningDataSourceActions.ReadScans
|
||||
},
|
||||
{
|
||||
label: "Trigger Scans",
|
||||
value: ProjectPermissionSecretScanningDataSourceActions.TriggerScans
|
||||
}
|
||||
]
|
||||
},
|
||||
[ProjectPermissionSub.SecretScanningFindings]: {
|
||||
title: "Secret Scanning Findings",
|
||||
actions: [
|
||||
{
|
||||
label: "Read Findings",
|
||||
value: ProjectPermissionSecretScanningFindingActions.Read
|
||||
},
|
||||
{
|
||||
label: "Update Findings",
|
||||
value: ProjectPermissionSecretScanningFindingActions.Update
|
||||
}
|
||||
]
|
||||
},
|
||||
[ProjectPermissionSub.SecretScanningConfigs]: {
|
||||
title: "Secret Scanning Config",
|
||||
actions: [
|
||||
{
|
||||
label: "Read Config",
|
||||
value: ProjectPermissionSecretScanningConfigActions.Read
|
||||
},
|
||||
{
|
||||
label: "Update Config",
|
||||
value: ProjectPermissionSecretScanningConfigActions.Update
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1371,6 +1526,12 @@ const SshPermissionSubjects = (enabled = false) => ({
|
||||
[ProjectPermissionSub.SshHostGroups]: enabled
|
||||
});
|
||||
|
||||
const SecretScanningSubject = (enabled = false) => ({
|
||||
[ProjectPermissionSub.SecretScanningDataSources]: enabled,
|
||||
[ProjectPermissionSub.SecretScanningFindings]: enabled,
|
||||
[ProjectPermissionSub.SecretScanningConfigs]: enabled
|
||||
});
|
||||
|
||||
// scott: this structure ensures we don't forget to add project permissions to their relevant project type
|
||||
export const ProjectTypePermissionSubjects: Record<
|
||||
ProjectType,
|
||||
@@ -1381,27 +1542,39 @@ export const ProjectTypePermissionSubjects: Record<
|
||||
...SecretsManagerPermissionSubjects(true),
|
||||
...KmsPermissionSubjects(),
|
||||
...CertificateManagerPermissionSubjects(),
|
||||
...SshPermissionSubjects()
|
||||
...SshPermissionSubjects(),
|
||||
...SecretScanningSubject()
|
||||
},
|
||||
[ProjectType.KMS]: {
|
||||
...SharedPermissionSubjects,
|
||||
...KmsPermissionSubjects(true),
|
||||
...SecretsManagerPermissionSubjects(),
|
||||
...CertificateManagerPermissionSubjects(),
|
||||
...SshPermissionSubjects()
|
||||
...SshPermissionSubjects(),
|
||||
...SecretScanningSubject()
|
||||
},
|
||||
[ProjectType.CertificateManager]: {
|
||||
...SharedPermissionSubjects,
|
||||
...CertificateManagerPermissionSubjects(true),
|
||||
...KmsPermissionSubjects(),
|
||||
...SecretsManagerPermissionSubjects(),
|
||||
...SshPermissionSubjects()
|
||||
...SshPermissionSubjects(),
|
||||
...SecretScanningSubject()
|
||||
},
|
||||
[ProjectType.SSH]: {
|
||||
...SharedPermissionSubjects,
|
||||
...SshPermissionSubjects(true),
|
||||
...CertificateManagerPermissionSubjects(),
|
||||
...KmsPermissionSubjects(),
|
||||
...SecretsManagerPermissionSubjects(),
|
||||
...SecretScanningSubject()
|
||||
},
|
||||
[ProjectType.SecretScanning]: {
|
||||
...SharedPermissionSubjects,
|
||||
...SecretScanningSubject(true),
|
||||
...SshPermissionSubjects(),
|
||||
...CertificateManagerPermissionSubjects(),
|
||||
...KmsPermissionSubjects(),
|
||||
...SecretsManagerPermissionSubjects()
|
||||
}
|
||||
};
|
||||
@@ -1614,6 +1787,56 @@ export const RoleTemplates: Record<ProjectType, RoleTemplate[]> = {
|
||||
},
|
||||
projectManagerTemplate()
|
||||
],
|
||||
[ProjectType.SecretScanning]: [
|
||||
{
|
||||
id: "scanning-viewer",
|
||||
name: "Secret Scanning Viewing Policies",
|
||||
description: "Grants read access to data sources and findings",
|
||||
permissions: [
|
||||
{
|
||||
subject: ProjectPermissionSub.SecretScanningDataSources,
|
||||
actions: [
|
||||
ProjectPermissionSecretScanningDataSourceActions.Read,
|
||||
ProjectPermissionSecretScanningDataSourceActions.ReadResources,
|
||||
ProjectPermissionSecretScanningDataSourceActions.ReadScans
|
||||
]
|
||||
},
|
||||
{
|
||||
subject: ProjectPermissionSub.SecretScanningFindings,
|
||||
actions: [ProjectPermissionSecretScanningFindingActions.Read]
|
||||
},
|
||||
{
|
||||
subject: ProjectPermissionSub.SecretScanningConfigs,
|
||||
actions: [ProjectPermissionSecretScanningConfigActions.Read]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "scanning-editor",
|
||||
name: "Secret Scanning Editing Policies",
|
||||
description: "Grants read and edit access to data sources and findings",
|
||||
permissions: [
|
||||
{
|
||||
subject: ProjectPermissionSub.SecretScanningDataSources,
|
||||
actions: Object.values(ProjectPermissionSecretScanningDataSourceActions)
|
||||
},
|
||||
{
|
||||
subject: ProjectPermissionSub.SecretScanningFindings,
|
||||
actions: Object.values(ProjectPermissionSecretScanningFindingActions)
|
||||
},
|
||||
{
|
||||
subject: ProjectPermissionSub.SecretScanningConfigs,
|
||||
actions: [ProjectPermissionSecretScanningConfigActions.Read]
|
||||
}
|
||||
]
|
||||
},
|
||||
projectManagerTemplate([
|
||||
{
|
||||
subject: ProjectPermissionSub.SecretScanningConfigs,
|
||||
actions: Object.values(ProjectPermissionSecretScanningConfigActions)
|
||||
}
|
||||
])
|
||||
],
|
||||
[ProjectType.SecretManager]: [
|
||||
{
|
||||
id: "secret-viewer",
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/roles/$roleSlug"
|
||||
)({
|
||||
component: RoleDetailsBySlugPage,
|
||||
beforeLoad: ({ context, params }) => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
...context.breadcrumbs,
|
||||
{
|
||||
label: "Access Control",
|
||||
link: linkOptions({
|
||||
to: "/secret-scanning/$projectId/access-management",
|
||||
params: {
|
||||
projectId: params.projectId
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: "Roles"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user